import { basename, extname } from "node:path";
import { promisify } from "node:util";
import { execFile } from "node:child_process";
import { prisma } from "../lib/prisma.js";
import { extractDocumentText } from "../lib/document-reviews.js";
import {
  consolidateCaseFacts,
  createDocumentAndExtraction,
  synthesizeClientKnowledgeBase,
} from "../lib/intelligence.js";
import { runFormEngineForClient } from "../lib/form-engine.js";

const execFileAsync = promisify(execFile);
const ZIP_CLIENT_PREFIX = "Employment Based Adjustment/clients/";

function getArgValue(flag: string) {
  const index = process.argv.indexOf(flag);
  if (index < 0) {
    return null;
  }
  return process.argv[index + 1] ?? null;
}

function hasFlag(flag: string) {
  return process.argv.includes(flag);
}

async function listZipEntries(zipPath: string) {
  const result = await execFileAsync("unzip", ["-Z1", zipPath], {
    encoding: "utf8",
    maxBuffer: 1024 * 1024 * 64,
  });

  return String(result.stdout)
    .split(/\r?\n/)
    .map((entry) => entry.trim())
    .filter(Boolean);
}

async function readZipEntryBuffer(zipPath: string, entryPath: string) {
  const result = await execFileAsync("unzip", ["-p", zipPath, entryPath], {
    encoding: "buffer" as BufferEncoding,
    maxBuffer: 1024 * 1024 * 128,
  });

  return Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.from(result.stdout);
}

function discoverClientFolders(entries: string[]) {
  return Array.from(
    new Set(
      entries
        .filter((entry) => entry.startsWith(ZIP_CLIENT_PREFIX))
        .map((entry) => entry.slice(ZIP_CLIENT_PREFIX.length).split("/")[0] ?? "")
        .filter(Boolean),
    ),
  ).sort((left, right) => left.localeCompare(right));
}

function inferMimeType(fileName: string) {
  switch (extname(fileName).toLowerCase()) {
    case ".json":
      return "application/json";
    case ".pdf":
      return "application/pdf";
    case ".txt":
      return "text/plain";
    case ".doc":
      return "application/msword";
    case ".docx":
      return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
    case ".jpg":
    case ".jpeg":
      return "image/jpeg";
    case ".png":
      return "image/png";
    default:
      return "application/octet-stream";
  }
}

function inferDocumentTypeCode(relativePath: string) {
  const normalized = relativePath.toLowerCase();

  if (normalized.includes("passport")) {
    return "passport";
  }
  if (normalized.includes("birth") && normalized.includes("certificate")) {
    return "birth_certificate";
  }
  if (normalized.includes("marriage") && normalized.includes("certificate")) {
    return "marriage_certificate";
  }
  if (/\bi[-_ ]?94\b/.test(normalized)) {
    return "i94";
  }
  if (normalized.includes("visa") || normalized.includes("ds-160") || normalized.includes("ds160")) {
    return "visa";
  }
  if (normalized.includes("resume") || normalized.includes("cv")) {
    return "resume";
  }
  if (normalized.includes("employment") || normalized.includes("offer letter")) {
    return "employment_letter";
  }
  if (normalized.includes("pay stub") || normalized.includes("paystub")) {
    return "pay_stub";
  }
  if (normalized.includes("tax") || normalized.includes("w2") || normalized.includes("1040")) {
    return "tax_return";
  }
  if (normalized.includes("photo")) {
    return "photo";
  }
  if (normalized.includes("translation")) {
    return "translation";
  }

  return "other_supporting";
}

function shouldImportEntry(entryPath: string, includeGenerated: boolean, onlyJson: boolean) {
  if (entryPath.endsWith("/")) {
    return false;
  }

  const normalized = entryPath.toLowerCase();
  const extension = extname(normalized);
  const supportedExtensions = new Set([".json", ".pdf", ".txt", ".doc", ".docx", ".jpg", ".jpeg", ".png"]);

  if (!supportedExtensions.has(extension)) {
    return false;
  }

  if (!includeGenerated && (normalized.includes("/ai generated/") || normalized.includes("/generated forms/"))) {
    return false;
  }

  if (onlyJson && extension !== ".json") {
    return false;
  }

  return true;
}

async function resolveClient(clientId: string) {
  const [client] = await prisma.$queryRaw<Array<{
    id: string;
    law_firm_id: string;
    first_name: string;
    last_name: string;
  }>>`
    SELECT id, law_firm_id, first_name, last_name
    FROM clients
    WHERE id = ${clientId}
    LIMIT 1
  `;

  return client ?? null;
}

async function resolveCaseId(lawFirmId: string, clientId: string, explicitCaseId: string | null) {
  if (explicitCaseId) {
    return explicitCaseId;
  }

  const [caseRow] = await prisma.$queryRaw<Array<{ id: string }>>`
    SELECT id
    FROM cases
    WHERE law_firm_id = ${lawFirmId}
      AND client_id = ${clientId}
      AND deleted_at IS NULL
    ORDER BY created_at DESC
    LIMIT 1
  `;

  return caseRow?.id ?? null;
}

async function resolveActorUserId(lawFirmId: string, explicitUserId: string | null) {
  if (explicitUserId) {
    return explicitUserId;
  }

  const [membership] = await prisma.$queryRaw<Array<{ user_id: string }>>`
    SELECT wm.user_id
    FROM workspace_memberships wm
    JOIN users u ON u.id = wm.user_id
    WHERE wm.law_firm_id = ${lawFirmId}
      AND u.deleted_at IS NULL
      AND u.is_active = 1
    ORDER BY wm.is_default DESC, wm.joined_at ASC
    LIMIT 1
  `;

  return membership?.user_id ?? null;
}

async function buildTextContent(fileName: string, mimeType: string, bytes: Buffer) {
  const extension = extname(fileName).toLowerCase();
  if (extension === ".json" || extension === ".txt") {
    return bytes.toString("utf8");
  }

  try {
    return await extractDocumentText({
      fileName,
      mimeType,
      bytes,
    });
  } catch {
    return null;
  }
}

async function main() {
  const zipPath = getArgValue("--zip-path");
  const clientId = getArgValue("--client-id");
  const clientFolderArg = getArgValue("--client-folder");
  const explicitCaseId = getArgValue("--case-id");
  const explicitLawFirmId = getArgValue("--law-firm-id");
  const explicitUserId = getArgValue("--user-id");
  const includeGenerated = hasFlag("--include-generated");
  const onlyJson = hasFlag("--only-json");
  const listClients = hasFlag("--list-clients");
  const dryRun = hasFlag("--dry-run");
  const generateI485 = hasFlag("--generate-i485");

  if (!zipPath) {
    throw new Error("Provide --zip-path with the absolute path to the export ZIP.");
  }

  const entries = await listZipEntries(zipPath);
  const clientFolders = discoverClientFolders(entries);

  if (listClients) {
    console.log(JSON.stringify({ zipPath, clientFolders }, null, 2));
    return;
  }

  if (!clientId) {
    throw new Error("Provide --client-id for the target client already registered in the system.");
  }

  const client = await resolveClient(clientId);
  if (!client) {
    throw new Error("Client not found.");
  }

  const lawFirmId = explicitLawFirmId ?? client.law_firm_id;
  const caseId = await resolveCaseId(lawFirmId, client.id, explicitCaseId);
  if (!caseId) {
    throw new Error("No case was found for this client. Provide --case-id.");
  }

  const actorUserId = await resolveActorUserId(lawFirmId, explicitUserId);
  if (!actorUserId) {
    throw new Error("No active user was found for the target law firm. Provide --user-id.");
  }

  const clientFolder =
    clientFolderArg ??
    (clientFolders.length === 1 ? clientFolders[0] : null);

  if (!clientFolder) {
    throw new Error(
      `The ZIP contains multiple client folders. Provide --client-folder. Options: ${clientFolders.join(", ")}`,
    );
  }

  const clientPrefix = `${ZIP_CLIENT_PREFIX}${clientFolder}/`;
  const importEntries = entries
    .filter((entry) => entry.startsWith(clientPrefix))
    .filter((entry) => shouldImportEntry(entry, includeGenerated, onlyJson));

  if (!importEntries.length) {
    throw new Error("No importable files were found for the selected client folder.");
  }

  if (dryRun) {
    console.log(
      JSON.stringify(
        {
          zipPath,
          clientId: client.id,
          caseId,
          clientFolder,
          importEntries,
        },
        null,
        2,
      ),
    );
    return;
  }

  const imported: Array<{
    entryPath: string;
    documentTypeCode: string;
    repositoryItemId: string;
    documentRecordId: string;
    extractionId: string | null;
    syncedClientFieldKeys: string[];
  }> = [];
  const failures: Array<{ entryPath: string; error: string }> = [];

  for (const entryPath of importEntries) {
    try {
      const bytes = await readZipEntryBuffer(zipPath, entryPath);
      const relativePath = entryPath.slice(clientPrefix.length);
      const fileName = basename(relativePath);
      const mimeType = inferMimeType(fileName);
      const textContent = await buildTextContent(fileName, mimeType, bytes);
      const documentTypeCode = inferDocumentTypeCode(relativePath);
      const forceStructuredJson = mimeType === "application/json";

      const created = await createDocumentAndExtraction({
        lawFirmId,
        clientId: client.id,
        caseId,
        actorUserId,
        title: `Legacy export • ${relativePath}`,
        documentTypeCode,
        documentTypeMode: forceStructuredJson ? "force" : "auto",
        originalFileName: fileName,
        mimeType,
        fileBuffer: bytes,
        textContent,
      });

      imported.push({
        entryPath,
        documentTypeCode: created.documentTypeCode,
        repositoryItemId: created.repositoryItemId,
        documentRecordId: created.documentRecordId,
        extractionId: created.extraction?.extractionId ?? null,
        syncedClientFieldKeys: created.syncedClientFieldKeys,
      });
    } catch (error) {
      failures.push({
        entryPath,
        error: error instanceof Error ? error.message : "Unexpected import error",
      });
    }
  }

  const consolidation = await consolidateCaseFacts({
    lawFirmId,
    caseId,
    actorUserId,
  });
  const synthesizedKnowledge = await synthesizeClientKnowledgeBase({
    lawFirmId,
    clientId: client.id,
    actorUserId,
    caseId,
    formName: "USCIS Form I-485",
  });
  const i485Generation =
    generateI485 ?
      await runFormEngineForClient({
        lawFirmId,
        actorUserId,
        clientId: client.id,
        caseId,
        agentCodes: ["uscis_i_485"],
        targetCoverage: 0.85,
        maxIterations: 2,
        maxRuntimeMs: 30 * 60 * 1000,
      })
    : null;

  console.log(
    JSON.stringify(
      {
        zipPath,
        client: {
          id: client.id,
          fullName: `${client.first_name} ${client.last_name}`.trim(),
        },
        caseId,
        clientFolder,
        importedCount: imported.length,
        failedCount: failures.length,
        imported,
        failures,
        consolidation: {
          aiRunId: consolidation.aiRunId,
          createdFactCount: consolidation.createdFactCount,
          factCount: consolidation.facts.length,
        },
        synthesizedKnowledge:
          synthesizedKnowledge ?
            {
              aiRunId: synthesizedKnowledge.aiRunId,
              repositoryItemId: synthesizedKnowledge.repositoryItemId,
              profileFactCount: synthesizedKnowledge.profileFacts.length,
              knowledgeFactCount: synthesizedKnowledge.knowledgeFacts.length,
              summaryText: synthesizedKnowledge.summaryText,
            }
          : null,
        i485Generation:
          i485Generation ?
            {
              runId: i485Generation.runId,
              results: i485Generation.results.map((result) => ({
                agentCode: result.agentCode,
                formName: result.formName,
                fileName: result.fileName,
                percentFilled: Number(result.metrics.percentFilled.toFixed(4)),
                percentHighConfidence: Number(result.metrics.percentHighConfidence.toFixed(4)),
                unresolvedFields: result.unresolvedFields.slice(0, 25),
              })),
            }
          : null,
      },
      null,
      2,
    ),
  );
}

main()
  .catch((error) => {
    console.error(error);
    process.exitCode = 1;
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
