import { prisma } from "./db";
import { Prisma } from "@/generated/prisma/client";
import { uploadBuffer, getBuffer, deleteObject } from "./storage";
import { ocrImage } from "./ai-ocr";
import { pdfToImages, imageToBase64, processUploadedImage } from "./pdf";
import { fireIntegrationEvent } from "./integrations";
import { linkCardToPerson } from "./person-linker";

const DELAY_BETWEEN_OCR_CALLS_MS = 2_000;
function ocrDelay() {
  return new Promise((r) => setTimeout(r, DELAY_BETWEEN_OCR_CALLS_MS));
}

export async function processFile(
  jobId: string,
  fileName: string,
  fileBuffer: Buffer,
  isPdf: boolean
): Promise<string[]> {
  const cardIds: string[] = [];

  try {
    const job = await prisma.processingJob.update({
      where: { id: jobId },
      data: { status: "processing" },
    });

    let pageImages: { buffer: Buffer; page: number }[];

    if (isPdf) {
      pageImages = await pdfToImages(fileBuffer);
      await prisma.processingJob.update({
        where: { id: jobId },
        data: { totalPages: pageImages.length },
      });
    } else {
      const processed = await processUploadedImage(fileBuffer);
      pageImages = [{ buffer: processed, page: 1 }];
      await prisma.processingJob.update({
        where: { id: jobId },
        data: { totalPages: 1 },
      });
    }

    const sourceKey = `sources/${jobId}/${fileName}`;
    await uploadBuffer(sourceKey, fileBuffer, isPdf ? "application/pdf" : "image/jpeg");

    console.log(`[ocr] ${fileName}: ${pageImages.length} page(s) extracted, isPdf=${isPdf}`);

    // Pair pages: page 1 = response card, page 2 = survey, etc.
    const pairs: { response?: typeof pageImages[0]; survey?: typeof pageImages[0] }[] = [];
    for (let i = 0; i < pageImages.length; i += 2) {
      pairs.push({
        response: pageImages[i],
        survey: pageImages[i + 1],
      });
    }

    // If single image (not PDF), treat as response card side
    if (!isPdf && pageImages.length === 1) {
      pairs.length = 0;
      pairs.push({ response: pageImages[0] });
    }

    console.log(`[ocr] ${fileName}: ${pairs.length} card pair(s), pages per pair: ${pairs.map((p, i) => `pair ${i}: response=${p.response ? "yes" : "no"}, survey=${p.survey ? "yes" : "no"}`).join("; ")}`);

    for (let pairIdx = 0; pairIdx < pairs.length; pairIdx++) {
      if (pairIdx > 0) await ocrDelay();
      const pair = pairs[pairIdx];
      const card = await prisma.responseCard.create({
        data: {
          sourceFile: sourceKey,
          ocrStatus: "processing",
          organizationId: job.organizationId,
        },
      });
      cardIds.push(card.id);

      try {
        let responseData: Record<string, unknown> = {};
        let surveyData: Record<string, unknown> = {};
        let totalConfidence = 0;
        let confidenceCount = 0;

        if (pair.response) {
          const imgKey = `images/${card.id}/response.jpg`;
          await uploadBuffer(imgKey, pair.response.buffer, "image/jpeg");
          await prisma.responseCard.update({
            where: { id: card.id },
            data: { backImagePath: imgKey },
          });

          const base64 = await imageToBase64(pair.response.buffer);
          const result = await ocrImage(base64, "response");
          responseData = result.data;
          totalConfidence += result.confidence;
          confidenceCount++;
        }

        if (pair.survey) {
          if (pair.response) await ocrDelay();

          const imgKey = `images/${card.id}/survey.jpg`;
          await uploadBuffer(imgKey, pair.survey.buffer, "image/jpeg");
          await prisma.responseCard.update({
            where: { id: card.id },
            data: { frontImagePath: imgKey },
          });

          const base64 = await imageToBase64(pair.survey.buffer);
          const result = await ocrImage(base64, "survey");
          surveyData = result.data;
          totalConfidence += result.confidence;
          confidenceCount++;
        }

        const avgConfidence = confidenceCount > 0 ? totalConfidence / confidenceCount : 0;

        const visitType = asString(responseData.visitType);
        const nextStepArr = surveyData.nextStep ?? [];
        const sunday = getPreviousSunday(card.createdAt);

        await prisma.responseCard.update({
          where: { id: card.id },
          data: {
            name: asString(responseData.name),
            gender: asString(responseData.gender),
            dateOfBirth: asString(responseData.dateOfBirth),
            maritalStatus: asString(responseData.maritalStatus),
            maritalStatusOther: asString(responseData.maritalStatusOther),
            visitType,
            cellPhone: asString(responseData.cellPhone),
            homePhone: asString(responseData.homePhone),
            email: asString(responseData.email),
            address: asString(responseData.address),
            aptNumber: asString(responseData.aptNumber),
            city: asString(responseData.city),
            state: asString(responseData.state),
            zip: asString(responseData.zip),
            prayerRequests: asString(responseData.prayerRequests),
            prayerForTeam: asBool(responseData.prayerForTeam),
            prayerConfidential: asBool(responseData.prayerConfidential),
            messageTopics: surveyData.messageTopics ?? [],
            messageTopicsOther: asString(surveyData.messageTopicsOther),
            nextStep: nextStepArr,
            attendanceDuration: asString(surveyData.attendanceDuration),
            campusPreference: surveyData.campusPreference ?? [],
            campusPreferenceOther: asString(surveyData.campusPreferenceOther),
            howHeard: surveyData.howHeard ?? [],
            howHeardOther: asString(surveyData.howHeardOther),
            serviceAttended: asString(surveyData.serviceAttended),
            firstTimeGuestDate: isFirstTimeGuest(visitType) ? sunday : null,
            salvationDate: hasBaptism(nextStepArr) ? sunday : null,
            ocrStatus: "complete",
            ocrConfidence: Math.round(avgConfidence),
            rawOcrResponse: JSON.parse(JSON.stringify({ response: responseData, survey: surveyData })),
          },
        });

        fireIntegrationEvent("ocr_complete", card.id).catch(() => {});

        if (job.organizationId) {
          linkCardToPerson(card.id, job.organizationId).catch((err) => {
            console.error("[person-linker] Failed to link card:", err);
          });
        }
      } catch (err) {
        const message = err instanceof Error ? err.message : "Unknown OCR error";
        await prisma.responseCard.update({
          where: { id: card.id },
          data: { ocrStatus: "error", ocrError: message },
        });

        fireIntegrationEvent("ocr_error", card.id).catch(() => {});
      }

      await prisma.processingJob.update({
        where: { id: jobId },
        data: { processed: { increment: 1 } },
      });
    }

    await prisma.processingJob.update({
      where: { id: jobId },
      data: { status: "complete", cardIds },
    });
  } catch (err) {
    const message = err instanceof Error ? err.message : "Unknown error";
    await prisma.processingJob.update({
      where: { id: jobId },
      data: { status: "error", error: message },
    });
  }

  return cardIds;
}

export async function reprocessCard(cardId: string): Promise<void> {
  const card = await prisma.responseCard.findUnique({ where: { id: cardId } });
  if (!card) throw new Error("Card not found");
  if (!card.backImagePath && !card.frontImagePath) {
    throw new Error("No stored images available for reprocessing");
  }

  await prisma.responseCard.update({
    where: { id: cardId },
    data: { ocrStatus: "processing", ocrError: null },
  });

  try {
    let responseData: Record<string, unknown> = {};
    let surveyData: Record<string, unknown> = {};
    let totalConfidence = 0;
    let confidenceCount = 0;

    if (card.backImagePath) {
      const imgBuffer = await getBuffer(card.backImagePath);
      const base64 = await imageToBase64(imgBuffer);
      const result = await ocrImage(base64, "response");
      responseData = result.data;
      totalConfidence += result.confidence;
      confidenceCount++;
    }

    if (card.frontImagePath) {
      if (card.backImagePath) await ocrDelay();

      const imgBuffer = await getBuffer(card.frontImagePath);
      const base64 = await imageToBase64(imgBuffer);
      const result = await ocrImage(base64, "survey");
      surveyData = result.data;
      totalConfidence += result.confidence;
      confidenceCount++;
    }

    const avgConfidence = confidenceCount > 0 ? totalConfidence / confidenceCount : 0;

    const visitType = asString(responseData.visitType);
    const nextStepArr = surveyData.nextStep ?? [];
    const sunday = getPreviousSunday(card.createdAt);

    await prisma.responseCard.update({
      where: { id: cardId },
      data: {
        name: asString(responseData.name),
        gender: asString(responseData.gender),
        dateOfBirth: asString(responseData.dateOfBirth),
        maritalStatus: asString(responseData.maritalStatus),
        maritalStatusOther: asString(responseData.maritalStatusOther),
        visitType,
        cellPhone: asString(responseData.cellPhone),
        homePhone: asString(responseData.homePhone),
        email: asString(responseData.email),
        address: asString(responseData.address),
        aptNumber: asString(responseData.aptNumber),
        city: asString(responseData.city),
        state: asString(responseData.state),
        zip: asString(responseData.zip),
        prayerRequests: asString(responseData.prayerRequests),
        prayerForTeam: asBool(responseData.prayerForTeam),
        prayerConfidential: asBool(responseData.prayerConfidential),
        messageTopics: surveyData.messageTopics ?? [],
        messageTopicsOther: asString(surveyData.messageTopicsOther),
        nextStep: nextStepArr,
        attendanceDuration: asString(surveyData.attendanceDuration),
        campusPreference: surveyData.campusPreference ?? [],
        campusPreferenceOther: asString(surveyData.campusPreferenceOther),
        howHeard: surveyData.howHeard ?? [],
        howHeardOther: asString(surveyData.howHeardOther),
        serviceAttended: asString(surveyData.serviceAttended),
        firstTimeGuestDate: isFirstTimeGuest(visitType) ? sunday : null,
        salvationDate: hasBaptism(nextStepArr) ? sunday : null,
        ocrStatus: "complete",
        ocrConfidence: Math.round(avgConfidence),
        ocrError: null,
        rawOcrResponse: JSON.parse(JSON.stringify({ response: responseData, survey: surveyData })),
      },
    });

    if (card.organizationId) {
      linkCardToPerson(card.id, card.organizationId).catch((err) => {
        console.error("[person-linker] Failed to link card:", err);
      });
    }
  } catch (err) {
    const message = err instanceof Error ? err.message : "Unknown OCR error";
    await prisma.responseCard.update({
      where: { id: cardId },
      data: { ocrStatus: "error", ocrError: message },
    });
  }
}

export async function reprocessJob(jobId: string): Promise<void> {
  const job = await prisma.processingJob.findUnique({ where: { id: jobId } });
  if (!job) throw new Error("Job not found");

  const oldCardIds = (job.cardIds as string[] | null) ?? [];
  for (const cardId of oldCardIds) {
    const card = await prisma.responseCard.findUnique({ where: { id: cardId } });
    if (card) {
      const deletes: Promise<void>[] = [];
      if (card.frontImagePath) deletes.push(deleteObject(card.frontImagePath));
      if (card.backImagePath) deletes.push(deleteObject(card.backImagePath));
      await Promise.allSettled(deletes);
      await prisma.responseCard.delete({ where: { id: cardId } });
    }
  }

  const sourceKey = `sources/${jobId}/${job.fileName}`;
  const fileBuffer = await getBuffer(sourceKey);
  const isPdf = job.fileName.toLowerCase().endsWith(".pdf");

  await prisma.processingJob.update({
    where: { id: jobId },
    data: { status: "queued", processed: 0, error: null, cardIds: Prisma.DbNull },
  });

  await processFile(jobId, job.fileName, fileBuffer, isPdf);
}

function asString(v: unknown): string | null {
  if (v === null || v === undefined) return null;
  return String(v);
}

function asBool(v: unknown): boolean {
  return v === true;
}

function getPreviousSunday(from: Date): Date {
  const d = new Date(from);
  const day = d.getDay();
  const diff = day === 0 ? 0 : day;
  d.setDate(d.getDate() - diff);
  d.setHours(0, 0, 0, 0);
  return d;
}

function isFirstTimeGuest(visitType: string | null): boolean {
  if (!visitType) return false;
  const lower = visitType.toLowerCase();
  return lower.includes("first") || lower.includes("second") || lower.includes("guest");
}

function hasBaptism(nextStep: unknown): boolean {
  if (!Array.isArray(nextStep)) return false;
  return nextStep.some((s) => {
    if (typeof s !== "string") return false;
    const lower = s.toLowerCase();
    return lower.includes("baptism") || lower.includes("expressing my faith");
  });
}
