import { readFile } from "node:fs/promises";
import path from "node:path";
import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
import fontkit from "@pdf-lib/fontkit";
import * as XLSX from "xlsx";
import { and, asc, desc, eq, gte, inArray, lt, sql } from "drizzle-orm";
import {
  courses,
  departments,
  employees,
  positions,
  trainingMatrices,
  trainingRecords,
} from "@/db/schema";
import {
  getTrainingCategoryLabel,
  getTrainingStatusLabel,
  getTrainingTypeLabel,
} from "@/constants/thai-labels";
import { db } from "@/lib/db";
import { requireOrganizationAccess } from "@/lib/auth/session";
import { getEmployeeIdsForUser } from "@/features/employees/server/employee-identity-linking";
import { getResolvedTrainingTargetsForEmployees } from "@/features/training-policy/server/employee-training-target-data";
import type {
  AnnualTrainingSummaryRow,
  DepartmentSummaryRow,
  EmployeeTranscriptRow,
  ReportFilters,
  ReportsDataset,
  ReportsOptions,
  TrainingMatrixReportRow,
} from "../api/types";

type Scope = {
  organizationId: string;
  role: string;
  userId: string;
};

type ReportKey =
  | "employeeTranscript"
  | "trainingMatrix"
  | "departmentSummary"
  | "annualTrainingSummary";

const THAI_MONTH_LABELS = [
  "ม.ค.",
  "ก.พ.",
  "มี.ค.",
  "เม.ย.",
  "พ.ค.",
  "มิ.ย.",
  "ก.ค.",
  "ส.ค.",
  "ก.ย.",
  "ต.ค.",
  "พ.ย.",
  "ธ.ค.",
] as const;

const THAI_FONT_PATH = path.join(
  process.cwd(),
  "src",
  "assets",
  "fonts",
  "THSarabunNew.ttf",
);
const employeeCompanyExpr = sql<string>`coalesce(${employees.company}, ${employees.companyName})`;

export function canViewAllReports(role: string) {
  return role === "owner" || role === "admin";
}

export function parseReportFilters(
  searchParams: Record<string, string | string[] | undefined>,
): ReportFilters {
  const currentYear = new Date().getUTCFullYear();
  const yearValue = Array.isArray(searchParams.year)
    ? searchParams.year[0]
    : searchParams.year;
  const companyValue = Array.isArray(searchParams.company)
    ? searchParams.company[0]
    : searchParams.company;
  const departmentIdValue = Array.isArray(searchParams.departmentId)
    ? searchParams.departmentId[0]
    : searchParams.departmentId;

  const parsedYear = Number(yearValue ?? currentYear);
  const parsedDepartmentId = departmentIdValue
    ? Number(departmentIdValue)
    : undefined;

  return {
    year:
      Number.isFinite(parsedYear) && parsedYear > 2000
        ? parsedYear
        : currentYear,
    company: companyValue?.trim() ? companyValue.trim() : undefined,
    departmentId:
      parsedDepartmentId !== undefined && Number.isFinite(parsedDepartmentId)
        ? parsedDepartmentId
        : undefined,
  };
}

function getYearRange(year: number) {
  return {
    start: new Date(Date.UTC(year, 0, 1)),
    end: new Date(Date.UTC(year + 1, 0, 1)),
  };
}

async function getScope(): Promise<Scope> {
  const { membership, organization, session } =
    await requireOrganizationAccess();

  return {
    organizationId: organization.id,
    role: membership.role,
    userId: session.user.id,
  };
}

export async function getReportsAccess() {
  const scope = await getScope();
  const canViewAll = canViewAllReports(scope.role);

  return {
    ...scope,
    canView: true,
    canViewAll,
  };
}

function buildOptions(
  rows: Array<{ id: number | string; label: string }>,
): ReportsOptions[keyof ReportsOptions] {
  return rows.map((row) => ({
    value: String(row.id),
    label: row.label,
  }));
}

async function getScopedEmployeeIds(scope: Scope, filters?: ReportFilters) {
  if (canViewAllReports(scope.role)) {
    const rows = await db
      .select({ id: employees.id })
      .from(employees)
      .where(
        and(
          eq(employees.organizationId, scope.organizationId),
          eq(employees.isActive, true),
          ...(filters?.company
            ? [eq(employeeCompanyExpr, filters.company)]
            : []),
          ...(filters?.departmentId
            ? [eq(employees.departmentId, filters.departmentId)]
            : []),
        ),
      );

    return rows.map((row) => row.id);
  }

  const selfEmployeeIds = await getEmployeeIdsForUser({
    organizationId: scope.organizationId,
    userId: scope.userId,
  });

  if (selfEmployeeIds.length === 0) {
    return [];
  }

  const rows = await db
    .select({ id: employees.id })
    .from(employees)
    .where(
      and(
        eq(employees.organizationId, scope.organizationId),
        inArray(employees.id, selfEmployeeIds),
        ...(filters?.company ? [eq(employeeCompanyExpr, filters.company)] : []),
        ...(filters?.departmentId
          ? [eq(employees.departmentId, filters.departmentId)]
          : []),
      ),
    );

  return rows.map((row) => row.id);
}

export async function getReportsOptions(
  organizationId: string,
  scope?: Pick<Scope, "role" | "userId">,
): Promise<ReportsOptions> {
  const selfScope = scope
    ? {
        organizationId,
        role: scope.role,
        userId: scope.userId,
      }
    : null;
  const scopedEmployeeIds = selfScope
    ? await getScopedEmployeeIds(selfScope)
    : [];
  const restrictToSelf = Boolean(
    selfScope && !canViewAllReports(selfScope.role),
  );

  const [yearRows, companyRows, departmentRows] = await Promise.all([
    db
      .select({
        year: sql<number>`extract(year from ${trainingRecords.trainingDate})::int`,
      })
      .from(trainingRecords)
      .where(eq(trainingRecords.organizationId, organizationId))
      .groupBy(sql`extract(year from ${trainingRecords.trainingDate})`)
      .orderBy(desc(sql`extract(year from ${trainingRecords.trainingDate})`)),
    db
      .select({
        company: employeeCompanyExpr,
      })
      .from(employees)
      .where(
        and(
          eq(employees.organizationId, organizationId),
          eq(employees.isActive, true),
          ...(restrictToSelf && scopedEmployeeIds.length > 0
            ? [inArray(employees.id, scopedEmployeeIds)]
            : []),
          ...(restrictToSelf && scopedEmployeeIds.length === 0
            ? [eq(employees.id, -1)]
            : []),
        ),
      )
      .groupBy(employeeCompanyExpr)
      .orderBy(asc(employeeCompanyExpr)),
    db
      .select({
        id: departments.id,
        name: departments.name,
      })
      .from(departments)
      .where(
        and(
          eq(departments.organizationId, organizationId),
          eq(departments.isActive, true),
        ),
      )
      .orderBy(asc(departments.name)),
  ]);

  const currentYear = new Date().getUTCFullYear();
  const uniqueYears = new Set([
    currentYear,
    ...yearRows.map((row) => row.year),
  ]);

  return {
    years: [...uniqueYears]
      .toSorted((a, b) => b - a)
      .map((year) => ({
        value: String(year),
        label: String(year + 543),
      })),
    companies: companyRows
      .filter((row): row is { company: string } => Boolean(row.company))
      .map((row) => ({
        value: row.company,
        label: row.company,
      })),
    departments: buildOptions(
      departmentRows.map((row) => ({
        id: row.id,
        label: row.name,
      })),
    ),
  };
}

async function getEmployeeTranscriptRows(
  filters: ReportFilters,
  scope: Scope,
): Promise<EmployeeTranscriptRow[]> {
  const { start, end } = getYearRange(filters.year);
  const scopedEmployeeIds = await getScopedEmployeeIds(scope, filters);

  if (scopedEmployeeIds.length === 0) {
    return [];
  }

  const rows = await db
    .select({
      recordId: trainingRecords.id,
      employeeId: employees.id,
      employeeName: employees.fullName,
      employeeCode: employees.employeeCode,
      companyName: employeeCompanyExpr,
      departmentName: departments.name,
      courseName: trainingRecords.courseName,
      trainingType: trainingRecords.trainingType,
      category: trainingRecords.category,
      trainingDate: trainingRecords.trainingDate,
      submittedHours: trainingRecords.hours,
      approvedHours: trainingRecords.approvedHours,
      status: trainingRecords.approvalStatus,
    })
    .from(trainingRecords)
    .innerJoin(employees, eq(trainingRecords.employeeId, employees.id))
    .leftJoin(departments, eq(employees.departmentId, departments.id))
    .where(
      and(
        eq(trainingRecords.organizationId, scope.organizationId),
        gte(trainingRecords.trainingDate, start),
        lt(trainingRecords.trainingDate, end),
        inArray(trainingRecords.employeeId, scopedEmployeeIds),
      ),
    )
    .orderBy(
      desc(trainingRecords.createdAt),
      asc(employees.fullName),
      asc(trainingRecords.courseName),
    );

  return rows.map((row) => ({
    recordId: row.recordId,
    employeeId: row.employeeId,
    employeeName: row.employeeName,
    employeeCode: row.employeeCode,
    companyName: row.companyName,
    departmentName: row.departmentName,
    courseName: row.courseName,
    trainingType: row.trainingType,
    category: row.category,
    trainingDate: row.trainingDate.toISOString(),
    submittedHours: row.submittedHours,
    approvedHours: row.approvedHours,
    status: row.status,
  }));
}

async function getDepartmentSummaryRows(
  filters: ReportFilters,
  scope: Scope,
): Promise<DepartmentSummaryRow[]> {
  if (!canViewAllReports(scope.role)) {
    return [];
  }

  const { start, end } = getYearRange(filters.year);
  const scopedEmployeeIds = await getScopedEmployeeIds(scope, filters);

  if (scopedEmployeeIds.length === 0) {
    return [];
  }

  const [employeeRows, reportRows] = await Promise.all([
    db
      .select({
        departmentName: sql<string>`coalesce(${departments.name}, 'ไม่ระบุแผนก')`,
        employeeCount: sql<number>`count(*)::int`,
      })
      .from(employees)
      .leftJoin(departments, eq(employees.departmentId, departments.id))
      .where(
        and(
          eq(employees.organizationId, scope.organizationId),
          eq(employees.isActive, true),
          inArray(employees.id, scopedEmployeeIds),
        ),
      )
      .groupBy(sql`coalesce(${departments.name}, 'ไม่ระบุแผนก')`)
      .orderBy(asc(sql`coalesce(${departments.name}, 'ไม่ระบุแผนก')`)),
    db
      .select({
        departmentName: sql<string>`coalesce(${departments.name}, 'ไม่ระบุแผนก')`,
        trainingRecords: sql<number>`count(*)::int`,
        approvedRecords: sql<number>`count(*) filter (where ${trainingRecords.approvalStatus} = 'approved')::int`,
        pendingRecords: sql<number>`count(*) filter (where ${trainingRecords.approvalStatus} in ('pending', 'needs_revision'))::int`,
        rejectedRecords: sql<number>`count(*) filter (where ${trainingRecords.approvalStatus} = 'rejected')::int`,
        approvedHours: sql<number>`coalesce(sum(case when ${trainingRecords.approvalStatus} = 'approved' then coalesce(${trainingRecords.approvedHours}, ${trainingRecords.hours}) else 0 end), 0)::float`,
      })
      .from(trainingRecords)
      .innerJoin(employees, eq(trainingRecords.employeeId, employees.id))
      .leftJoin(departments, eq(employees.departmentId, departments.id))
      .where(
        and(
          eq(trainingRecords.organizationId, scope.organizationId),
          gte(trainingRecords.trainingDate, start),
          lt(trainingRecords.trainingDate, end),
          inArray(trainingRecords.employeeId, scopedEmployeeIds),
        ),
      )
      .groupBy(sql`coalesce(${departments.name}, 'ไม่ระบุแผนก')`),
  ]);

  const reportMap = new Map(reportRows.map((row) => [row.departmentName, row]));

  return employeeRows
    .map((employeeRow) => {
      const reportRow = reportMap.get(employeeRow.departmentName);

      return {
        departmentName: employeeRow.departmentName,
        employeeCount: employeeRow.employeeCount,
        trainingRecords: reportRow?.trainingRecords ?? 0,
        approvedRecords: reportRow?.approvedRecords ?? 0,
        pendingRecords: reportRow?.pendingRecords ?? 0,
        rejectedRecords: reportRow?.rejectedRecords ?? 0,
        approvedHours: Number(reportRow?.approvedHours ?? 0),
      };
    })
    .toSorted((a, b) => b.approvedHours - a.approvedHours);
}

async function getAnnualTrainingSummaryRows(
  filters: ReportFilters,
  scope: Scope,
): Promise<AnnualTrainingSummaryRow[]> {
  if (!canViewAllReports(scope.role)) {
    return [];
  }

  const { start, end } = getYearRange(filters.year);
  const scopedEmployeeIds = await getScopedEmployeeIds(scope, filters);

  const rows =
    scopedEmployeeIds.length === 0
      ? []
      : await db
          .select({
            monthNumber: sql<number>`extract(month from ${trainingRecords.trainingDate})::int`,
            submittedRecords: sql<number>`count(*)::int`,
            approvedRecords: sql<number>`count(*) filter (where ${trainingRecords.approvalStatus} = 'approved')::int`,
            pendingRecords: sql<number>`count(*) filter (where ${trainingRecords.approvalStatus} in ('pending', 'needs_revision'))::int`,
            rejectedRecords: sql<number>`count(*) filter (where ${trainingRecords.approvalStatus} = 'rejected')::int`,
            submittedHours: sql<number>`coalesce(sum(${trainingRecords.hours}), 0)::float`,
            approvedHours: sql<number>`coalesce(sum(case when ${trainingRecords.approvalStatus} = 'approved' then coalesce(${trainingRecords.approvedHours}, ${trainingRecords.hours}) else 0 end), 0)::float`,
          })
          .from(trainingRecords)
          .where(
            and(
              eq(trainingRecords.organizationId, scope.organizationId),
              gte(trainingRecords.trainingDate, start),
              lt(trainingRecords.trainingDate, end),
              inArray(trainingRecords.employeeId, scopedEmployeeIds),
            ),
          )
          .groupBy(sql`extract(month from ${trainingRecords.trainingDate})`)
          .orderBy(
            asc(sql`extract(month from ${trainingRecords.trainingDate})`),
          );

  const rowMap = new Map(rows.map((row) => [row.monthNumber, row]));

  return Array.from({ length: 12 }, (_, index) => {
    const monthNumber = index + 1;
    const row = rowMap.get(monthNumber);

    return {
      monthNumber,
      monthLabel: THAI_MONTH_LABELS[index],
      submittedRecords: row?.submittedRecords ?? 0,
      approvedRecords: row?.approvedRecords ?? 0,
      pendingRecords: row?.pendingRecords ?? 0,
      rejectedRecords: row?.rejectedRecords ?? 0,
      submittedHours: Number(row?.submittedHours ?? 0),
      approvedHours: Number(row?.approvedHours ?? 0),
    };
  });
}

async function getTrainingMatrixRows(
  filters: ReportFilters,
  scope: Scope,
): Promise<TrainingMatrixReportRow[]> {
  if (!canViewAllReports(scope.role)) {
    return [];
  }

  const { end: rangeEnd } = getYearRange(filters.year);
  const endIso = rangeEnd.toISOString();
  const query = sql<TrainingMatrixReportRow>`
    with employee_scope as (
      select
        e.id as employee_id,
        e.full_name as employee_name,
        e.employee_code as employee_code,
        coalesce(e.company, e.company_name) as company_name,
        e.department_id as department_id,
        coalesce(d.name, 'ไม่ระบุแผนก') as department_name,
        e.position_id as position_id,
        p.name as position_name
      from employees e
      left join departments d on e.department_id = d.id
      left join positions p on e.position_id = p.id
      where e.organization_id = ${scope.organizationId}
        and e.is_active = true
        ${filters.company ? sql`and coalesce(e.company, e.company_name) = ${filters.company}` : sql``}
        ${filters.departmentId ? sql`and e.department_id = ${filters.departmentId}` : sql``}
    ),
    required_assignments as (
      select
        es.employee_id,
        tm.course_id,
        c.name as course_name,
        min(coalesce(tm.renewal_period_months, c.certificate_validity_months)) as expiry_months
      from employee_scope es
      inner join training_matrices tm
        on tm.organization_id = ${scope.organizationId}
       and tm.is_required = true
       and (
         (tm.department_id is not null and tm.department_id = es.department_id)
         or
         (tm.position_id is not null and tm.position_id = es.position_id)
       )
      inner join courses c on c.id = tm.course_id
      group by es.employee_id, tm.course_id, c.name
    ),
    latest_approved as (
      select
        tr.employee_id as employee_id,
        tr.course_id,
        max(tr.training_date) as latest_training_date
      from training_records tr
      inner join employee_scope es on es.employee_id = tr.employee_id
      where tr.organization_id = ${scope.organizationId}
        and tr.approval_status = 'approved'
        and tr.course_id is not null
        and tr.training_date < ${endIso}::timestamptz
      group by tr.employee_id, tr.course_id
    ),
    matrix_summary as (
      select
        es.employee_id,
        es.employee_name,
        es.employee_code,
        es.company_name,
        es.department_name,
        es.position_name,
        count(ra.course_id)::int as required_courses,
        count(*) filter (
          where la.latest_training_date is not null
            and (
              ra.expiry_months is null
              or la.latest_training_date + make_interval(months => ra.expiry_months::int) > ${endIso}::timestamptz
            )
        )::int as completed_courses,
        string_agg(
          case
            when la.latest_training_date is null
              or (
                ra.expiry_months is not null
                and la.latest_training_date + make_interval(months => ra.expiry_months::int) <= ${endIso}::timestamptz
              )
            then ra.course_name
            else null
          end,
          ' | ' order by ra.course_name
        ) as missing_courses
      from employee_scope es
      left join required_assignments ra on ra.employee_id = es.employee_id
      left join latest_approved la
        on la.employee_id = ra.employee_id
       and la.course_id = ra.course_id
      group by
        es.employee_id,
        es.employee_name,
        es.employee_code,
        es.company_name,
        es.department_name,
        es.position_name
    )
    select
      employee_id as "employeeId",
      employee_name as "employeeName",
      employee_code as "employeeCode",
      company_name as "companyName",
      department_name as "departmentName",
      position_name as "positionName",
      required_courses as "requiredCourses",
      completed_courses as "completedCourses",
      case
        when required_courses = 0 then 100
        else round((completed_courses::numeric / required_courses::numeric) * 100)::int
      end as "compliancePercent",
      coalesce(missing_courses, '') as "missingCourses"
    from matrix_summary
    order by "compliancePercent" asc, "employeeName" asc
  `;

  const result = await db.execute(query);
  const rows = ("rows" in result ? result.rows : result) as unknown as Array<
    Omit<
      TrainingMatrixReportRow,
      "approvedHours" | "targetHours" | "targetSource" | "targetStatus"
    >
  >;
  const employeeIds = rows.map((row) => row.employeeId);

  if (employeeIds.length === 0) {
    return [];
  }

  const { start, end: rangeEndForApproved } = getYearRange(filters.year);

  const [approvedRows, targetMap] = await Promise.all([
    db
      .select({
        employeeId: trainingRecords.employeeId,
        approvedHours: sql<number>`coalesce(sum(coalesce(${trainingRecords.approvedHours}, ${trainingRecords.hours})), 0)::float`,
      })
      .from(trainingRecords)
      .where(
        and(
          eq(trainingRecords.organizationId, scope.organizationId),
          eq(trainingRecords.approvalStatus, "approved"),
          inArray(trainingRecords.employeeId, employeeIds),
          gte(trainingRecords.trainingDate, start),
          lt(trainingRecords.trainingDate, rangeEndForApproved),
        ),
      )
      .groupBy(trainingRecords.employeeId),
    getResolvedTrainingTargetsForEmployees({
      organizationId: scope.organizationId,
      employeeIds,
      year: filters.year,
    }),
  ]);

  const approvedMap = new Map(
    approvedRows
      .filter(
        (row): row is { employeeId: number; approvedHours: number } =>
          row.employeeId !== null,
      )
      .map((row) => [row.employeeId, Number(row.approvedHours ?? 0)]),
  );

  return rows.map((row) => {
    const approvedHours = approvedMap.get(row.employeeId) ?? 0;
    const target = targetMap.get(row.employeeId);
    const targetHours = Number(target?.totalHours ?? 0);

    return {
      ...row,
      approvedHours,
      targetHours,
      targetSource: target?.source ?? "default",
      targetStatus:
        targetHours > 0 && approvedHours >= targetHours
          ? "passed"
          : "not_passed",
    };
  });
}

export async function getReportsDataset(
  filters: ReportFilters,
  scopeOverride?: Scope,
): Promise<ReportsDataset & { role: string }> {
  const scope = scopeOverride ?? (await getScope());
  const options = await getReportsOptions(scope.organizationId, scope);

  const [
    employeeTranscript,
    departmentSummary,
    annualTrainingSummary,
    trainingMatrix,
  ] = await Promise.all([
    getEmployeeTranscriptRows(filters, scope),
    getDepartmentSummaryRows(filters, scope),
    getAnnualTrainingSummaryRows(filters, scope),
    getTrainingMatrixRows(filters, scope),
  ]);

  return {
    role: scope.role,
    filters,
    options,
    employeeTranscript,
    trainingMatrix,
    departmentSummary,
    annualTrainingSummary,
  };
}

type ExportTable = {
  title: string;
  headers: string[];
  rows: Array<Array<string | number | null>>;
};

function buildExportTable(
  report: ReportKey,
  data: ReportsDataset,
): ExportTable {
  switch (report) {
    case "employeeTranscript":
      return {
        title: "รายงานประวัติการอบรมรายบุคคล",
        headers: [
          "รหัสรายการ",
          "รหัสพนักงาน",
          "ชื่อพนักงาน",
          "บริษัท",
          "แผนก",
          "หลักสูตร",
          "ประเภท",
          "หมวด",
          "วันที่อบรม",
          "ชั่วโมงที่ส่ง",
          "ชั่วโมงที่อนุมัติ",
          "สถานะ",
        ],
        rows: data.employeeTranscript.map((row) => [
          row.recordId,
          row.employeeCode,
          row.employeeName,
          row.companyName,
          row.departmentName,
          row.courseName,
          getTrainingTypeLabel(row.trainingType),
          getTrainingCategoryLabel(row.category),
          new Date(row.trainingDate).toLocaleDateString("th-TH"),
          row.submittedHours,
          row.approvedHours,
          getTrainingStatusLabel(row.status),
        ]),
      };
    case "trainingMatrix":
      return {
        title: "รายงาน การอบรมตามตำแหน่ง",
        headers: [
          "รหัสพนักงาน",
          "ชื่อพนักงาน",
          "บริษัท",
          "แผนก",
          "ตำแหน่ง",
          "ชั่วโมงอนุมัติ",
          "เป้าหมาย",
          "สถานะเป้าหมาย",
          "หลักสูตรบังคับ",
          "หลักสูตรที่ครบ",
          "เปอร์เซ็นต์ความครบ",
          "หลักสูตรที่ยังขาด",
        ],
        rows: data.trainingMatrix.map((row) => [
          row.employeeCode,
          row.employeeName,
          row.companyName,
          row.departmentName,
          row.positionName,
          row.approvedHours,
          row.targetHours,
          row.targetStatus === "passed" ? "ผ่าน" : "ยังไม่ผ่าน",
          row.requiredCourses,
          row.completedCourses,
          `${row.compliancePercent}%`,
          row.missingCourses || "-",
        ]),
      };
    case "departmentSummary":
      return {
        title: "รายงานสรุปตามแผนก",
        headers: [
          "แผนก",
          "พนักงาน",
          "รายการอบรม",
          "อนุมัติแล้ว",
          "รอตรวจสอบ",
          "ไม่อนุมัติ",
          "ชั่วโมงที่อนุมัติ",
        ],
        rows: data.departmentSummary.map((row) => [
          row.departmentName,
          row.employeeCount,
          row.trainingRecords,
          row.approvedRecords,
          row.pendingRecords,
          row.rejectedRecords,
          row.approvedHours,
        ]),
      };
    case "annualTrainingSummary":
      return {
        title: "รายงานสรุปการอบรมประจำปี",
        headers: [
          "เดือน",
          "รายการที่ส่ง",
          "อนุมัติแล้ว",
          "รอตรวจสอบ",
          "ไม่อนุมัติ",
          "ชั่วโมงที่ส่ง",
          "ชั่วโมงที่อนุมัติ",
        ],
        rows: data.annualTrainingSummary.map((row) => [
          row.monthLabel,
          row.submittedRecords,
          row.approvedRecords,
          row.pendingRecords,
          row.rejectedRecords,
          row.submittedHours,
          row.approvedHours,
        ]),
      };
    default:
      return {
        title: "",
        headers: [],
        rows: [],
      };
  }
}

export function createExcelBuffer(report: ReportKey, data: ReportsDataset) {
  const table = buildExportTable(report, data);
  const workbook = XLSX.utils.book_new();
  const sheet = XLSX.utils.aoa_to_sheet([table.headers, ...table.rows]);
  XLSX.utils.book_append_sheet(workbook, sheet, table.title.slice(0, 31));
  return XLSX.write(workbook, { type: "buffer", bookType: "xlsx" });
}

export async function createPdfBuffer(report: ReportKey, data: ReportsDataset) {
  const table = buildExportTable(report, data);
  const pdfDoc = await PDFDocument.create();
  pdfDoc.registerFontkit(fontkit);

  let font;
  try {
    const fontBytes = await readFile(THAI_FONT_PATH);
    font = await pdfDoc.embedFont(fontBytes);
  } catch {
    font = await pdfDoc.embedFont(StandardFonts.Helvetica);
  }

  const pageSize = { width: 842, height: 595 };
  const margin = 32;
  const lineHeight = 16;
  const maxWidth = pageSize.width - margin * 2;
  const allLines = [
    table.title,
    `ปี ${data.filters.year + 543}${data.filters.company ? ` | บริษัท ${data.filters.company}` : ""}${data.filters.departmentId ? " | กรองตามแผนก" : ""}`,
    "",
    table.headers.join(" | "),
    ...table.rows.map((row) =>
      row.map((value) => (value === null ? "-" : String(value))).join(" | "),
    ),
  ];

  let page = pdfDoc.addPage([pageSize.width, pageSize.height]);
  let y = pageSize.height - margin;

  const drawLine = (line: string, fontSize = 14) => {
    if (y < margin + lineHeight) {
      page = pdfDoc.addPage([pageSize.width, pageSize.height]);
      y = pageSize.height - margin;
    }

    const clippedLine = line.length > 140 ? `${line.slice(0, 137)}...` : line;
    page.drawText(clippedLine, {
      x: margin,
      y,
      size: fontSize,
      font,
      color: rgb(0.12, 0.12, 0.12),
      maxWidth,
    });
    y -= lineHeight;
  };

  for (const [index, line] of allLines.entries()) {
    drawLine(line, index === 0 ? 18 : 14);
  }

  return Buffer.from(await pdfDoc.save());
}
