import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/db";
import { deleteObject } from "@/lib/storage";
import { fireIntegrationEvent } from "@/lib/integrations";
import { logActivity, diffCardFields } from "@/lib/activity-log";
import { requireApiAuthWithOrg, handleApiError } from "@/lib/api-auth";
import { can } from "@/lib/permissions";

export async function GET(
  _request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  try {
    const session = await requireApiAuthWithOrg();
    const { id } = await params;
    const card = await prisma.responseCard.findUnique({
      where: { id },
      include: {
        formTemplate: {
          include: { fields: { orderBy: { sortOrder: "asc" } } },
        },
      },
    });

    if (!card || card.organizationId !== session.user.orgId) {
      return NextResponse.json({ error: "Card not found" }, { status: 404 });
    }

    const frontImageUrl = card.frontImagePath
      ? `/api/images/${card.frontImagePath}`
      : null;
    const backImageUrl = card.backImagePath
      ? `/api/images/${card.backImagePath}`
      : null;

    return NextResponse.json({
      ...card,
      frontImageUrl,
      backImageUrl,
    });
  } catch (error) {
    return handleApiError(error);
  }
}

export async function PUT(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  try {
    const session = await requireApiAuthWithOrg();
    const user = session.user;

    const { id } = await params;
    const card = await prisma.responseCard.findUnique({
      where: { id },
    });

    if (!card || card.organizationId !== session.user.orgId) {
      return NextResponse.json({ error: "Card not found" }, { status: 404 });
    }

    // Editors and above can edit any card; reviewers can edit cards assigned
    // to them; viewers cannot edit at all.
    const canEditAnyCard = can(user.role, "cards.edit");
    const canReviewAssigned =
      can(user.role, "cards.review") && card.assignedToId === user.id;
    if (!canEditAnyCard && !canReviewAssigned) {
      return NextResponse.json(
        {
          error: canEditAnyCard === false && user.role === "reviewer"
            ? "You can only edit cards assigned to you"
            : "You don't have permission to edit cards",
        },
        { status: 403 }
      );
    }

    const body = await request.json().catch(() => ({}));
    const data: Record<string, unknown> = {};

    const stringFields = [
      "name",
      "firstName",
      "lastName",
      "gender",
      "dateOfBirth",
      "maritalStatus",
      "maritalStatusOther",
      "visitType",
      "cellPhone",
      "homePhone",
      "email",
      "address",
      "aptNumber",
      "city",
      "state",
      "zip",
      "prayerRequests",
      "messageTopicsOther",
      "attendanceDuration",
      "campusPreferenceOther",
      "howHeardOther",
      "serviceAttended",
      "followUp",
      "notes",
      "serviceTime",
      "planningCenter",
      "ocrStatus",
      "reviewStatus",
      "ocrError",
      "reviewNotes",
    ];
    for (const field of stringFields) {
      if (body[field] != null) data[field] = String(body[field]);
    }

    if (body.prayerForTeam != null) data.prayerForTeam = Boolean(body.prayerForTeam);
    if (body.prayerConfidential != null) data.prayerConfidential = Boolean(body.prayerConfidential);
    if (body.iSaidYesBookSent != null) data.iSaidYesBookSent = Boolean(body.iSaidYesBookSent);
    if (body.ftGuestLetterSent != null) data.ftGuestLetterSent = Boolean(body.ftGuestLetterSent);

    for (const dateField of ["firstTimeGuestDate", "salvationDate", "assignedAt", "reviewedAt"] as const) {
      if (body[dateField] !== undefined) {
        data[dateField] = body[dateField] ? new Date(body[dateField]) : null;
      }
    }
    if (body.ocrConfidence != null) data.ocrConfidence = Number(body.ocrConfidence);
    if (body.messageTopics != null) data.messageTopics = body.messageTopics;
    if (body.nextStep != null) data.nextStep = body.nextStep;
    if (body.campusPreference != null) data.campusPreference = body.campusPreference;
    if (body.howHeard != null) data.howHeard = body.howHeard;
    if (body.rawOcrResponse != null) data.rawOcrResponse = body.rawOcrResponse;
    if (body.fieldData !== undefined) data.fieldData = body.fieldData;

    // Promote any fieldData keys that match canonical top-level ResponseCard
    // columns up to those columns. Form templates may mark contact/address
    // fields as non-core, which means user edits land in `fieldData` only —
    // promotion keeps the top-level columns (used by the list view, search,
    // integrations, CSV export) in sync. Explicit body fields always win.
    if (body.fieldData && typeof body.fieldData === "object") {
      const fd = body.fieldData as Record<string, unknown>;
      const PROMOTABLE_STRING = new Set([
        "firstName", "lastName", "name",
        "gender", "dateOfBirth",
        "maritalStatus", "maritalStatusOther", "visitType",
        "cellPhone", "homePhone", "email",
        "address", "aptNumber", "city", "state", "zip",
        "prayerRequests", "messageTopicsOther", "attendanceDuration",
        "campusPreferenceOther", "howHeardOther", "serviceAttended",
        "followUp", "notes", "serviceTime", "planningCenter",
      ]);
      const PROMOTABLE_BOOL = new Set([
        "prayerForTeam", "prayerConfidential",
        "iSaidYesBookSent", "ftGuestLetterSent",
      ]);
      const PROMOTABLE_ARRAY = new Set([
        "messageTopics", "nextStep", "campusPreference", "howHeard",
      ]);
      const PROMOTABLE_DATE = new Set([
        "firstTimeGuestDate", "salvationDate",
      ]);
      for (const [k, v] of Object.entries(fd)) {
        if (k in data) continue;
        if (PROMOTABLE_STRING.has(k)) {
          data[k] = v == null || v === "" ? null : String(v);
        } else if (PROMOTABLE_BOOL.has(k)) {
          data[k] = Boolean(v);
        } else if (PROMOTABLE_ARRAY.has(k)) {
          data[k] = Array.isArray(v) ? v : null;
        } else if (PROMOTABLE_DATE.has(k)) {
          data[k] = v ? new Date(String(v)) : null;
        }
      }
    }

    // Keep the denormalized `name` field in sync with firstName/lastName.
    // `name` is read by the list view, search, sort, integrations, and CSV
    // export, so whenever first/last changes (and the caller didn't already
    // pass an explicit `name`), recompute it.
    if (("firstName" in data || "lastName" in data) && !("name" in data)) {
      const nextFirst =
        "firstName" in data ? (data.firstName as string | null) : card.firstName;
      const nextLast =
        "lastName" in data ? (data.lastName as string | null) : card.lastName;
      const combined = [nextFirst, nextLast].filter(Boolean).join(" ").trim();
      data.name = combined || null;
    }

    for (const assignField of ["assignedToId", "assignedById", "reviewedById"] as const) {
      if (body[assignField] !== undefined) {
        data[assignField] = body[assignField] || null;
      }
    }

    if (body.reviewStatus === "in_review" && card.reviewStatus === "assigned") {
      data.reviewStatus = "in_review";
    }

    if (body.reviewStatus === "reviewed") {
      data.reviewedById = user.id;
      data.reviewedAt = new Date();
    }

    const oldCard = card as unknown as Record<string, unknown>;

    const updated = await prisma.responseCard.update({
      where: { id },
      data: data as Parameters<typeof prisma.responseCard.update>[0]["data"],
    });

    const newCard = updated as unknown as Record<string, unknown>;
    const changes = diffCardFields(oldCard, newCard);
    if (changes.length > 0) {
      logActivity(
        id,
        "manual_edit",
        "user",
        `${changes.length} field(s) updated manually`,
        changes,
        user.id
      ).catch(() => {});
    }

    const oldStatus = card.reviewStatus;
    const newStatus = updated.reviewStatus;
    if (oldStatus !== newStatus) {
      if (newStatus === "reviewed") {
        fireIntegrationEvent("card_reviewed", id, { oldCard }).catch(() => {});
      } else if (newStatus === "exported") {
        fireIntegrationEvent("card_exported", id, { oldCard }).catch(() => {});
      }
    }

    return NextResponse.json(updated);
  } catch (error) {
    return handleApiError(error);
  }
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  try {
    const session = await requireApiAuthWithOrg("cards.delete");

    const { id } = await params;
    const card = await prisma.responseCard.findUnique({
      where: { id },
    });

    if (!card || card.organizationId !== session.user.orgId) {
      return NextResponse.json({ error: "Card not found" }, { status: 404 });
    }

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

    const deletePromises: Promise<void>[] = [];
    if (card.frontImagePath) deletePromises.push(deleteObject(card.frontImagePath));
    if (card.backImagePath) deletePromises.push(deleteObject(card.backImagePath));
    await Promise.allSettled(deletePromises);

    await prisma.responseCard.delete({
      where: { id },
    });

    return NextResponse.json({ success: true });
  } catch (error) {
    return handleApiError(error);
  }
}
