"use server";

import { revalidatePath } from "next/cache";
import { and, eq, sql } from "drizzle-orm";
import { z } from "zod";
import { db } from "@/db";
import { customers, proposals, proposalApprovers, trainingCourses, auditLogs } from "@/db/schema";
import { requireSession } from "@/lib/auth";
import { isRealDate } from "@/lib/period";
import { computeProposalStatus, resolveChain, type Decision } from "@/lib/proposals";
import { PROPOSAL_TYPES, proposalType, type ProposalDetails } from "@/lib/proposal-types";
import { canManageSettings, scopeFilter } from "@/lib/rbac";
import { diffFields, writeAudit } from "@/lib/audit";

export type ProposalResult = { ok?: string; info?: string; error?: string };

const uuid = z.string().uuid();

/** Đọc và kiểm các trường riêng của từng loại phiếu; trả về chuỗi giá trị đã chuẩn hoá hoặc thông báo lỗi. */
async function readDetails(
  session: Awaited<ReturnType<typeof requireSession>>,
  typeKey: string,
  formData: FormData,
  /** Giá trị đã lưu của phiếu đang được Ban giám đốc sửa — để giữ nguyên trường chọn khoá/khách khi phiếu cũ không lưu mã. */
  existing?: Record<string, string>,
): Promise<{ details?: ProposalDetails; error?: string }> {
  const type = proposalType(typeKey);
  if (!type) return { error: "Chọn loại phiếu đề xuất." };

  const values: Record<string, string> = {};
  for (const field of type.fields) {
    const raw = String(formData.get(`d_${field.key}`) ?? "").trim();
    if (!raw) {
      if (field.required) return { error: `Nhập “${field.label}”.` };
      continue;
    }
    if (raw.length > 500) return { error: `“${field.label}” tối đa 500 ký tự.` };

    // Sửa phiếu: chọn "Giữ nguyên" thì lấy lại đúng giá trị (và mã) đã lưu, không tra lại danh mục.
    if (raw === "KEEP" && existing && existing[field.key]) {
      values[field.key] = existing[field.key];
      if (existing[`${field.key}Id`]) values[`${field.key}Id`] = existing[`${field.key}Id`];
      continue;
    }

    if (field.kind === "date") {
      if (!isRealDate(raw)) return { error: `“${field.label}” không phải ngày hợp lệ.` };
      values[field.key] = raw;
    } else if (field.kind === "number" || field.kind === "money") {
      if (!/^\d{1,12}$/.test(raw)) return { error: `“${field.label}” phải là số nguyên không âm.` };
      values[field.key] = String(Number(raw));
    } else if (field.kind === "course") {
      if (raw === "NEW") {
        values[field.key] = "Khoá mới (mô tả trong nội dung)";
      } else {
        if (!uuid.safeParse(raw).success) return { error: "Khoá chuyển giao không hợp lệ." };
        const [course] = await db
          .select({ name: trainingCourses.name })
          .from(trainingCourses)
          .where(and(eq(trainingCourses.tenantId, session.tenantId), eq(trainingCourses.id, raw)))
          .limit(1);
        if (!course) return { error: "Khoá chuyển giao không hợp lệ." };
        values[field.key] = course.name;
        values[`${field.key}Id`] = raw;
      }
    } else if (field.kind === "customer") {
      if (!uuid.safeParse(raw).success) return { error: "Khách hàng không hợp lệ." };
      // Chỉ chọn được khách trong phạm vi của mình (cùng quy tắc xem danh sách khách).
      const scope = await scopeFilter(session, { tenantId: customers.tenantId, ownerId: customers.ownerId, territoryId: customers.territoryId });
      const [customer] = await db
        .select({ orgName: customers.orgName, code: customers.code })
        .from(customers)
        .where(and(scope, eq(customers.id, raw)))
        .limit(1);
      if (!customer) return { error: "Khách hàng không hợp lệ hoặc ngoài phạm vi của bạn." };
      values[field.key] = `${customer.orgName} (${customer.code})`;
      values[`${field.key}Id`] = raw;
    } else {
      values[field.key] = raw;
    }
  }
  if (values.from && values.to && values.to < values.from) return { error: "Ngày kết thúc không được trước ngày bắt đầu." };
  return { details: { type: type.key, values } };
}

const baseSchema = z.object({
  title: z.string().trim().min(3, "Tiêu đề quá ngắn.").max(200, "Tiêu đề tối đa 200 ký tự."),
  content: z.string().trim().min(2, "Nội dung quá ngắn.").max(2000, "Nội dung tối đa 2000 ký tự."),
});

export async function createProposalAction(formData: FormData): Promise<ProposalResult> {
  const session = await requireSession();

  const parsed = baseSchema.safeParse({ title: formData.get("title"), content: formData.get("content") });
  if (!parsed.success) return { error: parsed.error.issues[0].message };

  const detailResult = await readDetails(session, String(formData.get("type") ?? ""), formData);
  if (!detailResult.details) return { error: detailResult.error ?? "Phiếu không hợp lệ." };
  const details = detailResult.details;

  const ids = (name: string) => formData.getAll(name).map(String).filter(Boolean);
  const related = ids("relatedDept");
  const extra = ids("extraApprover");
  const directorId = String(formData.get("directorId") ?? "");
  if ([...related, ...extra, directorId].filter(Boolean).some((id) => !uuid.safeParse(id).success)) return { error: "Danh sách người duyệt không hợp lệ." };

  const chain = await resolveChain(session, { relatedDepartmentIds: related, extraApproverIds: extra, directorId });
  if (chain.error) return { error: chain.error };

  const kind = PROPOSAL_TYPES.find((t) => t.key === details.type)!.label;
  const [proposal] = await db
    .insert(proposals)
    .values({ tenantId: session.tenantId, title: parsed.data.title, kind, content: parsed.data.content, details, createdBy: session.userId })
    .returning();

  await db.insert(proposalApprovers).values(
    chain.steps.flatMap((step, stepIndex) =>
      step.userIds.map((approverId) => ({
        tenantId: session.tenantId,
        proposalId: proposal.id,
        approverId,
        sortOrder: stepIndex,
        stepLabel: step.label,
      })),
    ),
  );

  await db.insert(auditLogs).values({
    tenantId: session.tenantId,
    userId: session.userId,
    action: "CREATE",
    entityType: "proposal",
    entityId: proposal.id,
    payload: { title: parsed.data.title, kind, steps: chain.steps.map((s) => `${s.label} (${s.userIds.length})`).join(" → ") },
  });

  revalidatePath("/cong-viec/de-xuat");
  return { ok: "Đã gửi đề xuất — chờ Trưởng bộ phận duyệt đầu tiên." };
}

/** Người duyệt quyết định ở ĐÚNG lượt của mình: bước trước phải đã duyệt, chưa quyết định trước đó, phiếu còn mở. Từ chối phải có lý do. */
export async function decideProposalAction(formData: FormData): Promise<ProposalResult> {
  const session = await requireSession();

  const proposalId = String(formData.get("proposalId") ?? "");
  const decision = String(formData.get("decision") ?? "");
  const note = String(formData.get("note") ?? "").trim();
  if (!uuid.safeParse(proposalId).success) return { error: "Không tìm thấy phiếu đề xuất." };
  if (decision !== "APPROVED" && decision !== "REJECTED") return { error: "Quyết định không hợp lệ." };
  if (decision === "REJECTED" && note.length < 3) return { error: "Nhập lý do từ chối." };
  if (note.length > 500) return { error: "Ghi chú tối đa 500 ký tự." };

  const [proposal] = await db
    .select({ id: proposals.id, title: proposals.title, status: proposals.status })
    .from(proposals)
    .where(and(eq(proposals.tenantId, session.tenantId), eq(proposals.id, proposalId)))
    .limit(1);
  if (!proposal) return { error: "Không tìm thấy phiếu đề xuất." };
  if (proposal.status !== "PENDING") return { info: "Phiếu này đã có kết quả — trang đã được làm mới." };

  const rows = await db
    .select({ id: proposalApprovers.id, approverId: proposalApprovers.approverId, decision: proposalApprovers.decision, step: proposalApprovers.sortOrder })
    .from(proposalApprovers)
    .where(and(eq(proposalApprovers.tenantId, session.tenantId), eq(proposalApprovers.proposalId, proposalId)));
  const mine = rows.find((row) => row.approverId === session.userId);
  if (!mine) return { error: "Bạn không nằm trong chuỗi duyệt của phiếu này." };
  if (mine.decision !== "PENDING") return { info: "Bạn đã quyết định phiếu này rồi." };
  if (rows.some((row) => row.step < mine.step && row.decision !== "APPROVED")) {
    return { error: "Chưa đến lượt bạn — các bước duyệt trước chưa duyệt xong." };
  }

  // Cập nhật có điều kiện `decision = PENDING` để bấm đúp / hai tab không ghi đè quyết định đã có.
  const updated = await db
    .update(proposalApprovers)
    .set({ decision: decision as Decision, note: note || null, decidedAt: new Date() })
    .where(and(eq(proposalApprovers.id, mine.id), eq(proposalApprovers.decision, "PENDING")))
    .returning();
  if (updated.length === 0) return { info: "Bạn đã quyết định phiếu này rồi." };

  const after = rows.map((row) => (row.id === mine.id ? (decision as Decision) : row.decision));
  const status = computeProposalStatus(after);
  await db
    .update(proposals)
    .set({ status, decidedAt: status === "PENDING" ? null : new Date() })
    .where(and(eq(proposals.tenantId, session.tenantId), eq(proposals.id, proposalId)));

  await db.insert(auditLogs).values({
    tenantId: session.tenantId,
    userId: session.userId,
    action: "UPDATE",
    entityType: "proposal",
    entityId: proposalId,
    payload: { title: proposal.title, decision, note: note || null, status },
  });

  revalidatePath("/cong-viec/de-xuat");
  const done = status === "APPROVED" ? " — phiếu đã được duyệt đủ." : status === "REJECTED" ? " — phiếu bị từ chối." : " — chuyển sang người duyệt kế tiếp.";
  return { ok: (decision === "APPROVED" ? "Đã duyệt" : "Đã từ chối") + done };
}

/**
 * Người đề xuất thu hồi phiếu khi CHƯA AI quyết định (nhập sai, đổi ý). Thông tin cần duyệt không được xoá (yêu cầu của khách) nên phiếu KHÔNG bị xoá:
 * chuyển sang trạng thái kết thúc và đánh dấu "đã thu hồi" trong `details`, vẫn xem lại được. Phiếu đã có người duyệt/từ chối thì không thu hồi được.
 */
export async function withdrawProposalAction(formData: FormData): Promise<ProposalResult> {
  const session = await requireSession();
  const proposalId = String(formData.get("proposalId") ?? "");
  if (!uuid.safeParse(proposalId).success) return { error: "Không tìm thấy phiếu đề xuất." };

  const [proposal] = await db
    .select({ id: proposals.id, title: proposals.title, status: proposals.status, createdBy: proposals.createdBy, details: proposals.details })
    .from(proposals)
    .where(and(eq(proposals.tenantId, session.tenantId), eq(proposals.id, proposalId)))
    .limit(1);
  if (!proposal || proposal.createdBy !== session.userId) return { error: "Chỉ người đề xuất mới thu hồi được phiếu của mình." };

  const [{ decided }] = await db
    .select({ decided: sql<number>`(count(*) filter (where ${proposalApprovers.decision} <> 'PENDING'))::int` })
    .from(proposalApprovers)
    .where(eq(proposalApprovers.proposalId, proposalId));
  if (proposal.status !== "PENDING" || Number(decided) > 0) return { error: "Phiếu đã có người duyệt/từ chối nên không thu hồi được." };

  const now = new Date();
  await db
    .update(proposals)
    .set({ status: "REJECTED", decidedAt: now, details: { ...((proposal.details as ProposalDetails | null) ?? { type: "OTHER", values: {} }), withdrawnAt: now.toISOString() } })
    .where(and(eq(proposals.tenantId, session.tenantId), eq(proposals.id, proposalId), eq(proposals.status, "PENDING")));
  await db.insert(auditLogs).values({
    tenantId: session.tenantId,
    userId: session.userId,
    action: "UPDATE",
    entityType: "proposal",
    entityId: proposalId,
    payload: { title: proposal.title, status: "WITHDRAWN" },
  });

  revalidatePath("/cong-viec/de-xuat");
  return { ok: "Đã thu hồi phiếu đề xuất." };
}

/* -------------------------------------------------------------------------- */
/* Ngoại lệ của Ban giám đốc: sửa / xoá phiếu đã gửi                          */
/* -------------------------------------------------------------------------- */

const ADMIN_ONLY = "Phiếu đề xuất đã gửi không sửa hay xoá được. Chỉ Ban giám đốc mới được điều chỉnh.";

/**
 * Yêu cầu của khách: thông tin cần duyệt (bán hàng, đề xuất) không ai được sửa hay xoá NGOẠI TRỪ Admin.
 * Người đề xuất và người duyệt không có thao tác sửa/xoá nào (chỉ thu hồi khi chưa ai quyết định); Ban giám đốc sửa được nội dung phiếu ở bất kỳ
 * trạng thái nào. Sửa KHÔNG đổi chuỗi duyệt hay kết quả duyệt — chỉ tiêu đề, nội dung và các trường riêng. Mọi thay đổi ghi nhật ký "cũ → mới".
 */
export async function updateProposalAction(formData: FormData): Promise<ProposalResult> {
  const session = await requireSession();
  if (!canManageSettings(session)) return { error: ADMIN_ONLY };

  const proposalId = String(formData.get("proposalId") ?? "");
  if (!uuid.safeParse(proposalId).success) return { error: "Không tìm thấy phiếu đề xuất." };

  const [proposal] = await db
    .select({ id: proposals.id, title: proposals.title, content: proposals.content, details: proposals.details })
    .from(proposals)
    .where(and(eq(proposals.tenantId, session.tenantId), eq(proposals.id, proposalId)))
    .limit(1);
  if (!proposal) return { error: "Không tìm thấy phiếu đề xuất." };

  const parsed = baseSchema.safeParse({ title: formData.get("title"), content: formData.get("content") });
  if (!parsed.success) return { error: parsed.error.issues[0].message };

  const oldDetails = (proposal.details as ProposalDetails | null) ?? { type: "OTHER" as const, values: {} };
  const detailResult = await readDetails(session, oldDetails.type, formData, oldDetails.values);
  if (!detailResult.details) return { error: detailResult.error ?? "Phiếu không hợp lệ." };

  const type = proposalType(oldDetails.type);
  const labelOf = (key: string) => type?.fields.find((f) => f.key === key)?.label ?? key;
  const oldValues = oldDetails.values ?? {};
  const newValues = detailResult.details.values;
  const fieldChanges: Record<string, { from: unknown; to: unknown }> = {};
  for (const key of new Set([...Object.keys(oldValues), ...Object.keys(newValues)])) {
    if (key.endsWith("Id")) continue; // mã kèm theo của trường chọn khoá/khách — tên hiển thị đã đủ để so
    if ((oldValues[key] ?? "") !== (newValues[key] ?? "")) fieldChanges[labelOf(key)] = { from: oldValues[key] ?? null, to: newValues[key] ?? null };
  }
  const changes = { ...diffFields({ title: proposal.title, content: proposal.content }, { title: parsed.data.title, content: parsed.data.content }), ...fieldChanges };
  if (Object.keys(changes).length === 0) return { info: "Không có thay đổi nào để lưu." };

  // Giữ nguyên các dấu vết khác trong `details` (ví dụ đã thu hồi), chỉ thay giá trị các trường.
  await db
    .update(proposals)
    .set({ title: parsed.data.title, content: parsed.data.content, details: { ...oldDetails, type: oldDetails.type, values: newValues } })
    .where(and(eq(proposals.tenantId, session.tenantId), eq(proposals.id, proposalId)));
  await writeAudit(session, "UPDATE", "proposal", proposalId, { title: parsed.data.title, changes });

  revalidatePath("/cong-viec/de-xuat");
  return { ok: "Đã cập nhật phiếu đề xuất." };
}

/** Ban giám đốc xoá hẳn một phiếu (nhập nhầm, phiếu thử). Nhật ký giữ lại toàn bộ nội dung và kết quả duyệt của phiếu bị xoá. */
export async function deleteProposalAction(formData: FormData): Promise<ProposalResult> {
  const session = await requireSession();
  if (!canManageSettings(session)) return { error: ADMIN_ONLY };

  const proposalId = String(formData.get("proposalId") ?? "");
  if (!uuid.safeParse(proposalId).success) return { error: "Không tìm thấy phiếu đề xuất." };

  const [proposal] = await db
    .select()
    .from(proposals)
    .where(and(eq(proposals.tenantId, session.tenantId), eq(proposals.id, proposalId)))
    .limit(1);
  if (!proposal) return { error: "Phiếu đã bị xoá hoặc không tồn tại — hãy tải lại trang." };

  const approvers = await db
    .select({ approverId: proposalApprovers.approverId, step: proposalApprovers.stepLabel, decision: proposalApprovers.decision, note: proposalApprovers.note })
    .from(proposalApprovers)
    .where(eq(proposalApprovers.proposalId, proposalId));

  // Ghi nhật ký TRƯỚC khi xoá — sau đó không còn gì để đối chiếu.
  await writeAudit(session, "DELETE", "proposal", proposalId, {
    title: proposal.title,
    kind: proposal.kind,
    status: proposal.status,
    createdBy: proposal.createdBy,
    content: proposal.content,
    values: (proposal.details as ProposalDetails | null)?.values ?? {},
    approvers: approvers.map((a) => `${a.step ?? "Người duyệt"} (${a.approverId}): ${a.decision}${a.note ? ` — ${a.note}` : ""}`),
  });
  await db.delete(proposalApprovers).where(eq(proposalApprovers.proposalId, proposalId));
  await db.delete(proposals).where(and(eq(proposals.tenantId, session.tenantId), eq(proposals.id, proposalId)));

  revalidatePath("/cong-viec/de-xuat");
  return { ok: "Đã xoá phiếu đề xuất." };
}
