import type { FastifyBaseLogger } from "fastify";
import { Prisma } from "@prisma/client";
import { generateClientFormFromTemplate } from "./intelligence.js";
import { createId } from "./id.js";
import { prisma } from "./prisma.js";
import { prepareSpecializedFormGeneration } from "./specialized-forms.js";

type SpecializedFormGenerationJobRow = {
  id: string;
  law_firm_id: string;
  client_id: string;
  form_template_id: string;
  form_name: string;
  agent_code: string;
  agent_prompt: string;
  created_by_user_id: string | null;
  status_code: string;
  stage_code: string;
  progress_percent: number | string | bigint;
  detail_text: string | null;
  filled_count: number | string | bigint;
  unresolved_count: number | string | bigint;
  unresolved_fields_json: string | null;
  generated_document_id: string | null;
  repository_item_id: string | null;
  file_id: string | null;
  file_name: string | null;
  error_message: string | null;
  started_at: Date | string | null;
  completed_at: Date | string | null;
  created_at: Date | string;
  updated_at: Date | string;
};

type SpecializedFormExplanationMetadata = {
  explanationGeneratedDocumentId: string | null;
  explanationRepositoryItemId: string | null;
  explanationFileId: string | null;
  explanationFileName: string | null;
};

export type SpecializedFormGenerationJobDetail = {
  id: string;
  clientId: string;
  templateId: string;
  formName: string;
  agentCode: string;
  statusCode: string;
  stageCode: string;
  progressPercent: number;
  detailText: string | null;
  filledCount: number;
  unresolvedCount: number;
  unresolvedFields: string[];
  generatedDocumentId: string | null;
  repositoryItemId: string | null;
  fileId: string | null;
  fileName: string | null;
  explanationGeneratedDocumentId: string | null;
  explanationRepositoryItemId: string | null;
  explanationFileId: string | null;
  explanationFileName: string | null;
  errorMessage: string | null;
  createdAt: string;
  updatedAt: string;
  startedAt: string | null;
  completedAt: string | null;
};

const activeSpecializedFormGenerationJobs = new Map<string, Promise<void>>();

function toNumber(value: number | bigint | string | null | undefined) {
  if (typeof value === "number") {
    return Number.isFinite(value) ? value : 0;
  }

  if (typeof value === "bigint") {
    return Number(value);
  }

  if (typeof value === "string" && value.trim()) {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : 0;
  }

  return 0;
}

function toIsoString(value: Date | string | null | undefined) {
  if (!value) {
    return null;
  }

  if (value instanceof Date) {
    return value.toISOString();
  }

  const parsed = new Date(value);
  return Number.isNaN(parsed.getTime()) ? String(value) : parsed.toISOString();
}

function parseUnresolvedFields(value: string | null) {
  if (!value) {
    return [];
  }

  try {
    const parsed = JSON.parse(value) as unknown;
    return Array.isArray(parsed) ? parsed.map((item) => String(item ?? "").trim()).filter(Boolean) : [];
  } catch {
    return [];
  }
}

function parseSpecializedFormExplanationMetadata(value: string | null): SpecializedFormExplanationMetadata {
  if (!value) {
    return {
      explanationGeneratedDocumentId: null,
      explanationRepositoryItemId: null,
      explanationFileId: null,
      explanationFileName: null,
    };
  }

  try {
    const parsed = JSON.parse(value) as {
      explanationReport?: {
        generatedDocumentId?: unknown;
        repositoryItemId?: unknown;
        fileId?: unknown;
        fileName?: unknown;
      };
    };

    return {
      explanationGeneratedDocumentId:
        String(parsed.explanationReport?.generatedDocumentId ?? "").trim() || null,
      explanationRepositoryItemId:
        String(parsed.explanationReport?.repositoryItemId ?? "").trim() || null,
      explanationFileId: String(parsed.explanationReport?.fileId ?? "").trim() || null,
      explanationFileName: String(parsed.explanationReport?.fileName ?? "").trim() || null,
    };
  } catch {
    return {
      explanationGeneratedDocumentId: null,
      explanationRepositoryItemId: null,
      explanationFileId: null,
      explanationFileName: null,
    };
  }
}

function buildSpecializedFormGenerationJobDetail(
  row: SpecializedFormGenerationJobRow,
  explanationMetadata?: SpecializedFormExplanationMetadata,
): SpecializedFormGenerationJobDetail {
  return {
    id: row.id,
    clientId: row.client_id,
    templateId: row.form_template_id,
    formName: row.form_name,
    agentCode: row.agent_code,
    statusCode: row.status_code,
    stageCode: row.stage_code,
    progressPercent: Math.max(0, Math.min(100, toNumber(row.progress_percent))),
    detailText: row.detail_text,
    filledCount: toNumber(row.filled_count),
    unresolvedCount: toNumber(row.unresolved_count),
    unresolvedFields: parseUnresolvedFields(row.unresolved_fields_json),
    generatedDocumentId: row.generated_document_id,
    repositoryItemId: row.repository_item_id,
    fileId: row.file_id,
    fileName: row.file_name,
    explanationGeneratedDocumentId: explanationMetadata?.explanationGeneratedDocumentId ?? null,
    explanationRepositoryItemId: explanationMetadata?.explanationRepositoryItemId ?? null,
    explanationFileId: explanationMetadata?.explanationFileId ?? null,
    explanationFileName: explanationMetadata?.explanationFileName ?? null,
    errorMessage: row.error_message,
    createdAt: toIsoString(row.created_at) ?? new Date().toISOString(),
    updatedAt: toIsoString(row.updated_at) ?? new Date().toISOString(),
    startedAt: toIsoString(row.started_at),
    completedAt: toIsoString(row.completed_at),
  };
}

async function loadSpecializedFormExplanationMetadata(repositoryItemId: string | null) {
  if (!repositoryItemId) {
    return parseSpecializedFormExplanationMetadata(null);
  }

  const [row] = await prisma.$queryRaw<Array<{ body_text: string | null }>>`
    SELECT body_text
    FROM repository_items
    WHERE id = ${repositoryItemId}
    LIMIT 1
  `;

  return parseSpecializedFormExplanationMetadata(row?.body_text ?? null);
}

async function getSpecializedFormGenerationJobRow(lawFirmId: string, jobId: string) {
  const [row] = await prisma.$queryRaw<Array<SpecializedFormGenerationJobRow>>`
    SELECT
      id,
      law_firm_id,
      client_id,
      form_template_id,
      form_name,
      agent_code,
      agent_prompt,
      created_by_user_id,
      status_code,
      stage_code,
      progress_percent,
      detail_text,
      filled_count,
      unresolved_count,
      unresolved_fields_json,
      generated_document_id,
      repository_item_id,
      file_id,
      file_name,
      error_message,
      started_at,
      completed_at,
      created_at,
      updated_at
    FROM specialized_form_generation_jobs
    WHERE id = ${jobId}
      AND law_firm_id = ${lawFirmId}
    LIMIT 1
  `;

  return row ?? null;
}

async function getSpecializedFormGenerationJobRowById(jobId: string) {
  const [row] = await prisma.$queryRaw<Array<SpecializedFormGenerationJobRow>>`
    SELECT
      id,
      law_firm_id,
      client_id,
      form_template_id,
      form_name,
      agent_code,
      agent_prompt,
      created_by_user_id,
      status_code,
      stage_code,
      progress_percent,
      detail_text,
      filled_count,
      unresolved_count,
      unresolved_fields_json,
      generated_document_id,
      repository_item_id,
      file_id,
      file_name,
      error_message,
      started_at,
      completed_at,
      created_at,
      updated_at
    FROM specialized_form_generation_jobs
    WHERE id = ${jobId}
    LIMIT 1
  `;

  return row ?? null;
}

async function ensureSpecializedFormGenerationJobCanRun(jobId: string) {
  const row = await getSpecializedFormGenerationJobRowById(jobId);
  if (!row) {
    return null;
  }

  if (row.status_code === "paused") {
    await updateSpecializedFormGenerationJob({
      jobId: row.id,
      stageCode: "paused",
      detailText: "Generation paused by the user.",
      errorMessage: null,
      markStarted: true,
    });
    return null;
  }

  return row;
}

async function updateSpecializedFormGenerationJob(input: {
  jobId: string;
  statusCode?: string;
  stageCode?: string;
  progressPercent?: number;
  detailText?: string | null;
  filledCount?: number;
  unresolvedCount?: number;
  unresolvedFields?: string[];
  generatedDocumentId?: string | null;
  repositoryItemId?: string | null;
  fileId?: string | null;
  fileName?: string | null;
  errorMessage?: string | null;
  markStarted?: boolean;
  markCompleted?: boolean;
}) {
  const assignments: Prisma.Sql[] = [Prisma.sql`updated_at = CURRENT_TIMESTAMP`];

  if (input.statusCode !== undefined) {
    assignments.push(Prisma.sql`status_code = ${input.statusCode}`);
  }
  if (input.stageCode !== undefined) {
    assignments.push(Prisma.sql`stage_code = ${input.stageCode}`);
  }
  if (input.progressPercent !== undefined) {
    assignments.push(Prisma.sql`progress_percent = ${Math.max(0, Math.min(100, input.progressPercent))}`);
  }
  if (input.detailText !== undefined) {
    assignments.push(Prisma.sql`detail_text = ${input.detailText}`);
  }
  if (input.filledCount !== undefined) {
    assignments.push(Prisma.sql`filled_count = ${Math.max(0, input.filledCount)}`);
  }
  if (input.unresolvedCount !== undefined) {
    assignments.push(Prisma.sql`unresolved_count = ${Math.max(0, input.unresolvedCount)}`);
  }
  if (input.unresolvedFields !== undefined) {
    assignments.push(Prisma.sql`unresolved_fields_json = ${JSON.stringify(input.unresolvedFields ?? [])}`);
  }
  if (input.generatedDocumentId !== undefined) {
    assignments.push(Prisma.sql`generated_document_id = ${input.generatedDocumentId}`);
  }
  if (input.repositoryItemId !== undefined) {
    assignments.push(Prisma.sql`repository_item_id = ${input.repositoryItemId}`);
  }
  if (input.fileId !== undefined) {
    assignments.push(Prisma.sql`file_id = ${input.fileId}`);
  }
  if (input.fileName !== undefined) {
    assignments.push(Prisma.sql`file_name = ${input.fileName}`);
  }
  if (input.errorMessage !== undefined) {
    assignments.push(Prisma.sql`error_message = ${input.errorMessage}`);
  }
  if (input.markStarted) {
    assignments.push(Prisma.sql`started_at = COALESCE(started_at, CURRENT_TIMESTAMP)`);
  }
  if (input.markCompleted) {
    assignments.push(Prisma.sql`completed_at = CURRENT_TIMESTAMP`);
  }

  await prisma.$executeRaw(
    Prisma.sql`
      UPDATE specialized_form_generation_jobs
      SET ${Prisma.join(assignments, ", ")}
      WHERE id = ${input.jobId}
    `,
  );
}

async function processSpecializedFormGenerationJobInternal(input: {
  jobId: string;
  logger?: FastifyBaseLogger | null;
}) {
  const row = await ensureSpecializedFormGenerationJobCanRun(input.jobId);
  if (!row) {
    return;
  }

  if (!row.created_by_user_id) {
    throw new Error("Specialized form generation job is missing the actor user");
  }

  await updateSpecializedFormGenerationJob({
    jobId: row.id,
    statusCode: "processing",
    stageCode: "loading_form",
    progressPercent: 5,
    detailText: "Loading the form template and client context.",
    errorMessage: null,
    markStarted: true,
  });

  try {
    const result = await generateClientFormFromTemplate({
      lawFirmId: row.law_firm_id,
      clientId: row.client_id,
      actorUserId: row.created_by_user_id,
      formTemplateId: row.form_template_id,
      formName: row.form_name,
      agentCode: row.agent_code,
      agentPrompt: row.agent_prompt,
      onProgress: async (progress) => {
        const refreshed = await ensureSpecializedFormGenerationJobCanRun(row.id);
        if (!refreshed) {
          throw new Error("__JOB_PAUSED__");
        }

        await updateSpecializedFormGenerationJob({
          jobId: row.id,
          statusCode: "processing",
          stageCode: progress.stageCode,
          progressPercent: progress.progressPercent,
          detailText: progress.detailText ?? null,
          markStarted: true,
        });
      },
    });

    const refreshed = await ensureSpecializedFormGenerationJobCanRun(row.id);
    if (!refreshed) {
      return;
    }

    await updateSpecializedFormGenerationJob({
      jobId: row.id,
      statusCode: "completed",
      stageCode: "completed",
      progressPercent: 100,
      detailText: "The filled form is ready and the browser download can start.",
      filledCount: result.filledCount,
      unresolvedCount: result.unresolvedCount,
      unresolvedFields: result.unresolvedFields,
      generatedDocumentId: result.generatedDocumentId,
      repositoryItemId: result.repositoryItemId,
      fileId: result.fileId,
      fileName: result.fileName,
      errorMessage: null,
      markStarted: true,
      markCompleted: true,
    });

    input.logger?.info(
      { jobId: row.id, agentCode: row.agent_code, clientId: row.client_id },
      "Specialized form generation job completed",
    );
  } catch (error) {
    if (error instanceof Error && error.message === "__JOB_PAUSED__") {
      await updateSpecializedFormGenerationJob({
        jobId: row.id,
        statusCode: "paused",
        stageCode: "paused",
        detailText: "Generation paused by the user.",
        errorMessage: null,
        markStarted: true,
      });

      input.logger?.info(
        { jobId: row.id, agentCode: row.agent_code, clientId: row.client_id },
        "Specialized form generation job paused",
      );
      return;
    }

    const message = error instanceof Error ? error.message : "Unexpected error";

    await updateSpecializedFormGenerationJob({
      jobId: row.id,
      statusCode: "failed",
      stageCode: "failed",
      progressPercent: 100,
      detailText: "The specialized form generation failed.",
      errorMessage: message,
      markStarted: true,
      markCompleted: true,
    });

    input.logger?.error(
      { error, jobId: row.id, agentCode: row.agent_code, clientId: row.client_id },
      "Specialized form generation job failed",
    );
  }
}

export async function createSpecializedFormGenerationJob(input: {
  lawFirmId: string;
  actorUserId: string;
  clientId: string;
  agentCode: string;
}) {
  const prepared = await prepareSpecializedFormGeneration({
    lawFirmId: input.lawFirmId,
    actorUserId: input.actorUserId,
    agentCode: input.agentCode,
  });

  const jobId = createId();

  await prisma.$executeRaw`
    INSERT INTO specialized_form_generation_jobs (
      id,
      law_firm_id,
      client_id,
      form_template_id,
      form_name,
      agent_code,
      agent_prompt,
      created_by_user_id,
      status_code,
      stage_code,
      progress_percent,
      detail_text,
      created_at,
      updated_at
    ) VALUES (
      ${jobId},
      ${input.lawFirmId},
      ${input.clientId},
      ${prepared.templateId},
      ${prepared.formName},
      ${prepared.agentCode},
      ${prepared.prompt},
      ${input.actorUserId},
      'queued',
      'queued',
      0,
      'The generation job was created and is waiting to start.',
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP
    )
  `;

  return jobId;
}

export async function startSpecializedFormGenerationJob(input: {
  jobId: string;
  logger?: FastifyBaseLogger | null;
}) {
  if (activeSpecializedFormGenerationJobs.has(input.jobId)) {
    return;
  }

  const task = processSpecializedFormGenerationJobInternal(input)
    .catch(async (error) => {
      const message = error instanceof Error ? error.message : "Unexpected error";
      await updateSpecializedFormGenerationJob({
        jobId: input.jobId,
        statusCode: "failed",
        stageCode: "failed",
        progressPercent: 100,
        detailText: "The specialized form generation failed.",
        errorMessage: message,
        markStarted: true,
        markCompleted: true,
      });
      input.logger?.error({ error, jobId: input.jobId }, "Unable to process specialized form job");
    })
    .finally(() => {
      activeSpecializedFormGenerationJobs.delete(input.jobId);
    });

  activeSpecializedFormGenerationJobs.set(input.jobId, task);
}

export async function pauseSpecializedFormGenerationJob(input: {
  lawFirmId: string;
  jobId: string;
}) {
  const row = await getSpecializedFormGenerationJobRow(input.lawFirmId, input.jobId);
  if (!row) {
    return null;
  }

  if (row.status_code === "completed" || row.status_code === "failed") {
    return buildSpecializedFormGenerationJobDetail(
      row,
      await loadSpecializedFormExplanationMetadata(row.repository_item_id),
    );
  }

  await updateSpecializedFormGenerationJob({
    jobId: row.id,
    statusCode: "paused",
    stageCode: "paused",
    detailText: "Generation paused by the user.",
    errorMessage: null,
    markStarted: true,
  });

  const refreshedRow = await getSpecializedFormGenerationJobRow(input.lawFirmId, input.jobId);
  return refreshedRow
    ? buildSpecializedFormGenerationJobDetail(
        refreshedRow,
        await loadSpecializedFormExplanationMetadata(refreshedRow.repository_item_id),
      )
    : null;
}

export async function resumeSpecializedFormGenerationJob(input: {
  lawFirmId: string;
  jobId: string;
  logger?: FastifyBaseLogger | null;
}) {
  const row = await getSpecializedFormGenerationJobRow(input.lawFirmId, input.jobId);
  if (!row) {
    return null;
  }

  if (row.status_code === "completed" || row.status_code === "failed") {
    return buildSpecializedFormGenerationJobDetail(
      row,
      await loadSpecializedFormExplanationMetadata(row.repository_item_id),
    );
  }

  await updateSpecializedFormGenerationJob({
    jobId: row.id,
    statusCode: "queued",
    stageCode: "queued",
    detailText: "Generation queued to resume.",
    errorMessage: null,
    markStarted: true,
  });

  await startSpecializedFormGenerationJob({
    jobId: row.id,
    logger: input.logger,
  });

  const refreshedRow = await getSpecializedFormGenerationJobRow(input.lawFirmId, input.jobId);
  return refreshedRow
    ? buildSpecializedFormGenerationJobDetail(
        refreshedRow,
        await loadSpecializedFormExplanationMetadata(refreshedRow.repository_item_id),
      )
    : null;
}

export async function getSpecializedFormGenerationJobDetail(input: {
  lawFirmId: string;
  jobId: string;
  logger?: FastifyBaseLogger | null;
}) {
  const row = await getSpecializedFormGenerationJobRow(input.lawFirmId, input.jobId);
  if (!row) {
    return null;
  }

  if (
    (row.status_code === "queued" || row.status_code === "processing") &&
    !activeSpecializedFormGenerationJobs.has(row.id)
  ) {
    await startSpecializedFormGenerationJob({
      jobId: row.id,
      logger: input.logger,
    });
  }

  const refreshedRow = await getSpecializedFormGenerationJobRow(input.lawFirmId, input.jobId);
  return refreshedRow
    ? buildSpecializedFormGenerationJobDetail(
        refreshedRow,
        await loadSpecializedFormExplanationMetadata(refreshedRow.repository_item_id),
      )
    : null;
}

export async function resumePendingSpecializedFormGenerationJobs(logger?: FastifyBaseLogger | null) {
  const jobs = await prisma.$queryRaw<Array<{ id: string }>>`
    SELECT id
    FROM specialized_form_generation_jobs
    WHERE status_code IN ('queued', 'processing')
  `;

  for (const job of jobs) {
    if (!activeSpecializedFormGenerationJobs.has(job.id)) {
      await startSpecializedFormGenerationJob({
        jobId: job.id,
        logger,
      });
    }
  }
}
