"use server";

import { revalidatePath } from "next/cache";
import { and, eq, sql } from "drizzle-orm";
import { z } from "zod";
import { db } from "@/db";
import { customKpis, customKpiEntries, customKpiLogs, auditLogs } from "@/db/schema";
import { requireSession } from "@/lib/auth";
import { canEditCatalog, visibleUserIds } from "@/lib/rbac";
import { today, isRealDate, isRealMonth } from "@/lib/period";
import { diffFields, writeAudit } from "@/lib/audit";
import { isUuid } from "@/lib/route-params";

const PATH = "/danh-muc/kpi-tu-tao";

const kpiSchema = z.object({
  name: z.string().trim().min(1, "Nhập tên KPI.").max(120),
  unit: z.string().trim().min(1, "Nhập đơn vị tính.").max(30),
});

export type KpiState = { error?: string; ok?: string };

export async function createKpiAction(_prev: KpiState, formData: FormData): Promise<KpiState> {
  const session = await requireSession();
  if (!canEditCatalog(session)) return { error: "Bạn không có quyền tạo KPI." };

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

  const [created] = await db
    .insert(customKpis)
    .values({
      tenantId: session.tenantId,
      name: parsed.data.name,
      unit: parsed.data.unit,
      createdBy: session.userId,
    })
    .returning();
  await writeAudit(session, "CREATE", "custom_kpi", created.id, { name: parsed.data.name, unit: parsed.data.unit });

  revalidatePath(PATH);
  return { ok: "Đã tạo KPI mới — giao chỉ tiêu cho NVKD ở bảng bên dưới." };
}

export async function deactivateKpiAction(formData: FormData): Promise<{ ok?: string; error?: string }> {
  const session = await requireSession();
  if (!canEditCatalog(session)) return { error: "Bạn không có quyền ẩn KPI." };

  const id = String(formData.get("id") ?? "");
  if (!isUuid(id)) return { error: "Không tìm thấy KPI." };
  const updated = await db
    .update(customKpis)
    .set({ isActive: false })
    .where(and(eq(customKpis.tenantId, session.tenantId), eq(customKpis.id, id), eq(customKpis.isActive, true)))
    .returning();
  if (updated.length === 0) return { error: "KPI không còn hoạt động hoặc không tồn tại." };
  await writeAudit(session, "UPDATE", "custom_kpi", id, { name: updated[0].name, changes: { isActive: { from: true, to: false } } });

  revalidatePath(PATH);
  return { ok: "Đã ngừng KPI." };
}

const assignSchema = z.object({
  kpiId: z.string().uuid("Chọn KPI."),
  userId: z.string().uuid("Chọn nhân viên."),
  periodKey: z.string().refine(isRealMonth, "Kỳ không hợp lệ."),
  targetValue: z.coerce.number().min(0, "Chỉ tiêu không được âm."),
});

export async function assignKpiAction(_prev: KpiState, formData: FormData): Promise<KpiState> {
  const session = await requireSession();
  if (!canEditCatalog(session)) return { error: "Bạn không có quyền giao KPI." };

  const parsed = assignSchema.safeParse({
    kpiId: formData.get("kpiId"),
    userId: formData.get("userId"),
    periodKey: formData.get("periodKey"),
    targetValue: formData.get("targetValue"),
  });
  if (!parsed.success) return { error: parsed.error.issues[0].message };
  const data = parsed.data;

  // Chỉ giao được cho NVKD trong phạm vi quản lý của mình.
  const allowedUserIds = await visibleUserIds(session);
  if (allowedUserIds && !allowedUserIds.includes(data.userId)) {
    return { error: "Bạn chỉ được giao KPI cho nhân viên mình phụ trách." };
  }

  // KPI phải thuộc công ty và còn hoạt động — không tin mã KPI do trình duyệt gửi lên.
  const [kpi] = await db
    .select({ name: customKpis.name })
    .from(customKpis)
    .where(and(eq(customKpis.tenantId, session.tenantId), eq(customKpis.id, data.kpiId), eq(customKpis.isActive, true)))
    .limit(1);
  if (!kpi) return { error: "KPI không tồn tại hoặc đã ngừng." };

  const [previous] = await db
    .select({ targetValue: customKpiEntries.targetValue })
    .from(customKpiEntries)
    .where(and(eq(customKpiEntries.kpiId, data.kpiId), eq(customKpiEntries.userId, data.userId), eq(customKpiEntries.periodKey, data.periodKey)))
    .limit(1);

  const [saved] = await db
    .insert(customKpiEntries)
    .values({
      tenantId: session.tenantId,
      kpiId: data.kpiId,
      userId: data.userId,
      periodKey: data.periodKey,
      targetValue: data.targetValue,
      updatedBy: session.userId,
    })
    .onConflictDoUpdate({
      target: [customKpiEntries.kpiId, customKpiEntries.userId, customKpiEntries.periodKey],
      set: { targetValue: data.targetValue, updatedBy: session.userId, updatedAt: new Date() },
    })
    .returning();
  await writeAudit(session, previous ? "UPDATE" : "CREATE", "custom_kpi_entry", saved.id, {
    name: kpi.name,
    userId: data.userId,
    periodKey: data.periodKey,
    ...(previous ? { changes: diffFields({ targetValue: previous.targetValue }, { targetValue: data.targetValue }) } : { targetValue: data.targetValue }),
  });

  revalidatePath(PATH);
  revalidatePath("/phan-tich");
  revalidatePath("/cong-viec");
  return { ok: "Đã giao chỉ tiêu." };
}

const actualSchema = z.object({
  entryId: z.string().uuid(),
  actualValue: z.coerce.number().min(0, "Kết quả không được âm."),
  note: z.string().trim().max(300).optional(),
});

export async function recordActualAction(formData: FormData) {
  const session = await requireSession();
  if (!canEditCatalog(session)) throw new Error("Bạn không có quyền ghi nhận kết quả KPI.");

  const parsed = actualSchema.safeParse({
    entryId: formData.get("entryId"),
    actualValue: formData.get("actualValue"),
    note: formData.get("note") || undefined,
  });
  if (!parsed.success) throw new Error(parsed.error.issues[0].message);
  const data = parsed.data;

  const [entry] = await db
    .select({ userId: customKpiEntries.userId })
    .from(customKpiEntries)
    .where(and(eq(customKpiEntries.tenantId, session.tenantId), eq(customKpiEntries.id, data.entryId)))
    .limit(1);
  if (!entry) throw new Error("Không tìm thấy chỉ tiêu.");

  const allowedUserIds = await visibleUserIds(session);
  if (allowedUserIds && !allowedUserIds.includes(entry.userId)) {
    throw new Error("Bạn chỉ được ghi nhận KPI cho nhân viên mình phụ trách.");
  }

  await db
    .update(customKpiEntries)
    .set({
      actualValue: data.actualValue,
      note: data.note ?? null,
      updatedBy: session.userId,
      updatedAt: new Date(),
    })
    .where(and(eq(customKpiEntries.tenantId, session.tenantId), eq(customKpiEntries.id, data.entryId)));

  await db.insert(auditLogs).values({
    tenantId: session.tenantId,
    userId: session.userId,
    action: "UPDATE",
    entityType: "custom_kpi_entry",
    entityId: data.entryId,
    payload: { actualValue: data.actualValue },
  });

  revalidatePath(PATH);
  revalidatePath("/phan-tich");
  revalidatePath("/cong-viec");
}

export async function deleteEntryAction(formData: FormData): Promise<{ ok?: string; error?: string }> {
  const session = await requireSession();
  if (!canEditCatalog(session)) return { error: "Bạn không có quyền xoá." };

  const id = String(formData.get("id") ?? "");
  if (!isUuid(id)) return { error: "Không tìm thấy dòng chỉ tiêu." };
  const [entry] = await db
    .select()
    .from(customKpiEntries)
    .where(and(eq(customKpiEntries.tenantId, session.tenantId), eq(customKpiEntries.id, id)))
    .limit(1);
  if (!entry) return { error: "Không tìm thấy dòng chỉ tiêu." };

  // Chỉ xoá được chỉ tiêu của nhân viên thuộc phạm vi mình (trước đây thiếu kiểm này: quản lý xoá được dòng của mọi người).
  const allowedUserIds = await visibleUserIds(session);
  if (allowedUserIds && !allowedUserIds.includes(entry.userId)) return { error: "Bạn chỉ được xoá chỉ tiêu của nhân viên mình phụ trách." };

  await writeAudit(session, "DELETE", "custom_kpi_entry", id, {
    userId: entry.userId,
    periodKey: entry.periodKey,
    targetValue: entry.targetValue,
    actualValue: entry.actualValue,
  });
  await db.delete(customKpiEntries).where(and(eq(customKpiEntries.tenantId, session.tenantId), eq(customKpiEntries.id, id)));

  revalidatePath(PATH);
  return { ok: "Đã xoá dòng chỉ tiêu." };
}

const logSchema = z.object({
  entryId: z.string().uuid(),
  value: z.coerce.number().positive("Số ghi nhận phải lớn hơn 0.").max(1_000_000, "Số quá lớn."),
  logDate: z.string().refine(isRealDate, "Ngày không hợp lệ."),
  note: z.string().trim().max(300).optional(),
});

export type KpiLogResult = { ok?: string; error?: string };

/**
 * Nhân viên báo cáo số đã làm trong ngày cho một KPI tự tạo được giao cho mình (yêu cầu mục 4:
 * "nhân viên báo cáo theo ngày, cộng dồn"). Ghi vào nhật ký và cộng dồn vào số thực hiện của tháng
 * chứa ngày đó; chỉ nhận ngày thuộc đúng tháng của chỉ tiêu, không nhận ngày tương lai.
 */
export async function logKpiProgressAction(formData: FormData): Promise<KpiLogResult> {
  const session = await requireSession();
  const parsed = logSchema.safeParse({
    entryId: formData.get("entryId"),
    value: formData.get("value"),
    logDate: formData.get("logDate") || today(),
    note: formData.get("note") || undefined,
  });
  if (!parsed.success) return { error: parsed.error.issues[0].message };
  const data = parsed.data;

  const [entry] = await db
    .select({
      id: customKpiEntries.id,
      userId: customKpiEntries.userId,
      periodKey: customKpiEntries.periodKey,
    })
    .from(customKpiEntries)
    .where(and(eq(customKpiEntries.tenantId, session.tenantId), eq(customKpiEntries.id, data.entryId)))
    .limit(1);
  if (!entry) return { error: "Không tìm thấy chỉ tiêu." };
  if (entry.userId !== session.userId) return { error: "Bạn chỉ ghi nhận được KPI được giao cho chính mình." };
  if (data.logDate.slice(0, 7) !== entry.periodKey) {
    return { error: `Ngày ghi nhận phải nằm trong tháng ${entry.periodKey} của chỉ tiêu.` };
  }
  if (data.logDate > today()) return { error: "Không ghi nhận cho ngày trong tương lai." };

  await db.insert(customKpiLogs).values({
    tenantId: session.tenantId,
    entryId: entry.id,
    userId: session.userId,
    logDate: data.logDate,
    value: data.value,
    note: data.note ?? null,
  });
  await db
    .update(customKpiEntries)
    // Cộng ngay trong SQL để hai lần ghi gần nhau không ghi đè lên nhau.
    .set({ actualValue: sql`${customKpiEntries.actualValue} + ${data.value}`, updatedBy: session.userId, updatedAt: new Date() })
    .where(and(eq(customKpiEntries.tenantId, session.tenantId), eq(customKpiEntries.id, entry.id)));

  await db.insert(auditLogs).values({
    tenantId: session.tenantId,
    userId: session.userId,
    action: "CREATE",
    entityType: "custom_kpi_log",
    entityId: entry.id,
    payload: { value: data.value, logDate: data.logDate },
  });

  revalidatePath("/cong-viec");
  revalidatePath(PATH);
  revalidatePath("/phan-tich");
  return { ok: `Đã ghi nhận +${data.value}.` };
}
