import { Prisma } from "@/app/generated/prisma/client";
import { prisma } from "@/lib/prisma";
import { organizerRepo } from "@/server/repositories/organizer.repo";
import { decryptString, encryptString, hmacHex } from "@/lib/crypto";

/** Field-field yang admin boleh minta upload ulang (Phase 2). */
export const REUPLOAD_FIELDS = [
  "ktpImage",
  "liveness",
  "nik",
  "bankInfo",
  "address",
] as const;
export type ReuploadField = (typeof REUPLOAD_FIELDS)[number];

export function isReuploadField(value: string): value is ReuploadField {
  return (REUPLOAD_FIELDS as readonly string[]).includes(value);
}

/**
 * Build array history rejection — append rejection sekarang (kalau ada) ke
 * `previousRejections` lama. Dipanggil saat organizer submit ulang.
 */
type RejectionEntry = { at: string; reason: string; submission: number };

function buildArchivedRejections(
  existing:
    | {
        status: string;
        rejectionReason: string | null;
        reviewedAt: Date | null;
        submissionCount: number;
        previousRejections: Prisma.JsonValue | null;
      }
    | null
): RejectionEntry[] {
  if (!existing) return [];
  const prior = Array.isArray(existing.previousRejections)
    ? (existing.previousRejections as unknown as RejectionEntry[])
    : [];
  if (
    existing.status === "REJECTED" &&
    existing.rejectionReason &&
    existing.reviewedAt
  ) {
    return [
      ...prior,
      {
        at: existing.reviewedAt.toISOString(),
        reason: existing.rejectionReason,
        submission: existing.submissionCount,
      },
    ];
  }
  return prior;
}

type SubmitInput = {
  fullName: string;
  nik: string;
  birthDate: Date;
  address: string;
  ktpImageKey: string;
  livenessKey: string;
  bankName: string;
  bankAccountNumber: string;
  bankAccountName: string;
};

export const organizerService = {
  async submitVerification(userId: string, data: SubmitInput) {
    const existing = await organizerRepo.findByUserId(userId);
    if (existing && existing.status === "APPROVED") {
      throw new Error("Akun kamu sudah terverifikasi");
    }
    // Phase 2: kalau status PENDING dengan reuploadRequested = true, allow
    // submit ulang (re-upload flow). Kalau PENDING biasa (belum di-review),
    // tolak supaya tidak overwrite submission yang sedang antri.
    if (
      existing &&
      existing.status === "PENDING" &&
      !existing.reuploadRequested
    ) {
      throw new Error("Pengajuan kamu masih dalam proses review");
    }

    const nikHash = hmacHex(data.nik);
    const dupNik = await organizerRepo.findByNikHash(nikHash);
    if (dupNik && dupNik.userId !== userId) {
      throw new Error("NIK ini sudah dipakai akun lain");
    }

    // Phase 3: bump submission count + arsip rejection lama kalau ada.
    const nextCount = existing ? existing.submissionCount + 1 : 1;
    const archivedRejections = buildArchivedRejections(existing);

    return organizerRepo.upsertForUser(userId, {
      fullName: data.fullName,
      nikEncrypted: encryptString(data.nik),
      nikHash,
      birthDate: data.birthDate,
      address: data.address,
      ktpImageKey: data.ktpImageKey,
      livenessKey: data.livenessKey,
      bankName: data.bankName,
      bankAccountNumber: data.bankAccountNumber,
      bankAccountName: data.bankAccountName,
      status: "PENDING",
      rejectionReason: null,
      reviewedAt: null,
      reviewedById: null,
      verifiedAt: null,
      reuploadRequested: false,
      reuploadFields: [],
      reuploadNote: null,
      submissionCount: nextCount,
      previousRejections: archivedRejections,
      isManualOverride: false,
      manualOverrideById: null,
      manualOverrideNote: null,
    });
  },

  async reviewVerification(input: {
    verificationId: string;
    decision: "APPROVED" | "REJECTED";
    rejectionReason?: string;
    reviewerId: string;
  }) {
    const verification = await organizerRepo.findById(input.verificationId);
    if (!verification) {
      throw new Error("Pengajuan tidak ditemukan");
    }
    if (verification.status !== "PENDING") {
      throw new Error("Pengajuan ini sudah diproses");
    }

    return organizerRepo.updateReview(input.verificationId, {
      status: input.decision,
      rejectionReason: input.decision === "REJECTED" ? input.rejectionReason ?? null : null,
      reviewedById: input.reviewerId,
    });
  },

  /** Append rejection reason ke `previousRejections` JSON array — dipakai
   *  saat organizer submit ulang (Phase 3 history). */
  async _archiveCurrentRejection(verificationId: string) {
    const v = await organizerRepo.findById(verificationId);
    if (!v || v.status !== "REJECTED" || !v.rejectionReason) return;
    const archived = buildArchivedRejections(v);
    await prisma.organizerVerification.update({
      where: { id: verificationId },
      data: { previousRejections: archived as Prisma.InputJsonValue },
    });
  },

  async getStatusForUser(userId: string) {
    return organizerRepo.findByUserId(userId);
  },

  async isApproved(userId: string) {
    const v = await organizerRepo.findByUserId(userId);
    return v?.status === "APPROVED";
  },

  /**
   * Phase 2: admin minta organizer upload ulang field tertentu (KTP buram,
   * liveness gelap, dst). Set flag tanpa drop submission — organizer melihat
   * banner kuning di /verify dan submit ulang dengan auto-clear flag.
   */
  async requestReupload(input: {
    verificationId: string;
    adminId: string;
    fields: ReuploadField[];
    note: string;
  }) {
    const verification = await organizerRepo.findById(input.verificationId);
    if (!verification) {
      throw new Error("Pengajuan tidak ditemukan");
    }
    if (verification.status === "APPROVED") {
      throw new Error("Verifikasi sudah disetujui — tidak perlu re-upload");
    }
    if (input.fields.length === 0) {
      throw new Error("Pilih minimal 1 field yang perlu di-upload ulang");
    }
    const invalid = input.fields.filter((f) => !isReuploadField(f));
    if (invalid.length > 0) {
      throw new Error(`Field tidak valid: ${invalid.join(", ")}`);
    }
    const trimmedNote = input.note.trim();
    if (trimmedNote.length < 10) {
      throw new Error("Catatan re-upload wajib min 10 karakter");
    }
    if (trimmedNote.length > 500) {
      throw new Error("Catatan re-upload maksimal 500 karakter");
    }

    return organizerRepo.requestReupload(input.verificationId, {
      fields: input.fields,
      note: trimmedNote,
    });
  },

  /**
   * Phase 4: admin verify organizer tanpa upload KYC (mis. partner trusted
   * referral). Buat row APPROVED dengan flag `isManualOverride = true` supaya
   * audit trail jelas — visible di admin UI dan tidak campur dengan KYC normal.
   */
  async manualOverrideVerification(input: {
    userId: string;
    adminId: string;
    note: string;
    bankName: string;
    bankAccountNumber: string;
    bankAccountName: string;
  }) {
    const existing = await organizerRepo.findByUserId(input.userId);
    if (existing) {
      throw new Error(
        "User sudah punya pengajuan verifikasi — pakai approve biasa atau reopen REJECTED."
      );
    }
    const trimmedNote = input.note.trim();
    if (trimmedNote.length < 10) {
      throw new Error(
        "Catatan manual override wajib min 10 karakter (alasan + ref partnership)"
      );
    }

    const now = new Date();
    // Placeholder NIK encrypted — tetap valid format tapi marker khusus.
    // Catatan: tidak ada NIK real, jadi nikHash juga placeholder unik per user
    // (pakai user.id sebagai HMAC input supaya tidak collide dengan NIK real).
    const placeholderNik = `OVERRIDE-${input.userId}`;
    return organizerRepo.createManualOverride({
      userId: input.userId,
      adminId: input.adminId,
      note: trimmedNote,
      fullName: input.bankAccountName,
      nikEncrypted: encryptString(placeholderNik),
      nikHash: hmacHex(placeholderNik),
      bankName: input.bankName,
      bankAccountNumber: input.bankAccountNumber,
      bankAccountName: input.bankAccountName,
      verifiedAt: now,
    });
  },

  /**
   * Buka kembali verifikasi yang REJECTED ke PENDING. Dipakai admin saat
   * organizer kirim foto/data baru via email/WA dan ingin di-review ulang
   * tanpa drop & recreate row.
   *
   * - `rejectionReason` lama disimpan di field — kalau di-reject lagi nanti,
   *   field-nya di-overwrite (audit trail via auditLog Phase 4 di roadmap).
   * - Reset `reviewedById`, `reviewedAt` supaya muncul lagi di tab PENDING.
   */
  async reopenVerification(input: {
    verificationId: string;
    adminId: string;
    note: string;
  }) {
    const verification = await organizerRepo.findById(input.verificationId);
    if (!verification) {
      throw new Error("Pengajuan tidak ditemukan");
    }
    if (verification.status !== "REJECTED") {
      throw new Error("Hanya pengajuan REJECTED yang bisa dibuka kembali");
    }
    const trimmedNote = input.note.trim();
    if (trimmedNote.length < 10) {
      throw new Error("Catatan reopen wajib min 10 karakter untuk audit");
    }
    if (trimmedNote.length > 500) {
      throw new Error("Catatan reopen maksimal 500 karakter");
    }

    return organizerRepo.reopen(input.verificationId, trimmedNote);
  },

  /** Reveal NIK plaintext. Caller must enforce authorization (owner or admin). */
  decryptNik(nikEncrypted: string): string {
    return decryptString(nikEncrypted);
  },
};
