import { db } from "../db";
import type { ClosureStatus, Payroll, PayrollStatus } from "../types";
import { findClosure, getClosureSummary, lockClosure } from "./closure";
import { listEmployees } from "./employees";
import { todayIso } from "../util/date";

export interface PayrollAdjustments {
  tips_amount: number;
  bonus_amount: number;
  other_amount: number;
}

export function findPayroll(employeeId: number, period: string): Payroll | undefined {
  return db
    .prepare<[number, string], Payroll>("SELECT * FROM payroll WHERE employee_id = ? AND period = ?")
    .get(employeeId, period);
}

/** Manager-side draft (tips/bonus/other) prepared before the month is locked. Frozen once locked. */
export function saveDraftAdjustments(employeeId: number, period: string, adjustments: PayrollAdjustments): Payroll {
  const closure = findClosure(employeeId, period);
  if (!closure || closure.status === "waiting_employee") {
    throw new Error("Odměny lze zadat až po potvrzení docházky zaměstnancem.");
  }
  const existing = findPayroll(employeeId, period);
  if (existing && existing.status !== "draft") {
    throw new Error("Mzdový podklad už byl uzamčen a nelze jej tímto způsobem měnit.");
  }
  for (const value of Object.values(adjustments)) {
    if (!Number.isFinite(value)) {
      throw new Error("Částka musí být číslo.");
    }
  }

  const summary = getClosureSummary(employeeId, period);
  const finalAmount =
    summary.earned_estimate + adjustments.tips_amount + adjustments.bonus_amount + adjustments.other_amount;

  upsertPayroll(employeeId, period, {
    worked_minutes: summary.worked_minutes,
    base_amount: summary.earned_estimate,
    tips_amount: adjustments.tips_amount,
    bonus_amount: adjustments.bonus_amount,
    other_amount: adjustments.other_amount,
    final_amount: finalAmount,
    status: "draft",
  });

  return findPayroll(employeeId, period)!;
}

/** Locks the month closure and freezes the payroll numbers (whatever draft adjustments existed, or zero). */
export function lockAndFinalizePayroll(employeeId: number, period: string): Payroll {
  lockClosure(employeeId, period);

  const summary = getClosureSummary(employeeId, period);
  const draft = findPayroll(employeeId, period);
  const tips = draft?.tips_amount ?? 0;
  const bonus = draft?.bonus_amount ?? 0;
  const other = draft?.other_amount ?? 0;
  const finalAmount = summary.earned_estimate + tips + bonus + other;

  upsertPayroll(employeeId, period, {
    worked_minutes: summary.worked_minutes,
    base_amount: summary.earned_estimate,
    tips_amount: tips,
    bonus_amount: bonus,
    other_amount: other,
    final_amount: finalAmount,
    status: "ready",
  });

  return findPayroll(employeeId, period)!;
}

/** Undoes a lock that turned out to be premature (e.g. the rate wasn't set yet) — back to an editable draft. */
export function reopenPayroll(employeeId: number, period: string): Payroll {
  const closure = findClosure(employeeId, period);
  const payroll = findPayroll(employeeId, period);
  if (!closure || closure.status !== "locked" || !payroll || payroll.status !== "ready") {
    throw new Error("Lze odemknout jen uzamčenou a dosud nevyplacenou mzdu.");
  }

  db.prepare("UPDATE month_closures SET status = 'confirmed', locked_at = NULL WHERE id = ?").run(closure.id);

  const summary = getClosureSummary(employeeId, period);
  const finalAmount = summary.earned_estimate + payroll.tips_amount + payroll.bonus_amount + payroll.other_amount;
  upsertPayroll(employeeId, period, {
    worked_minutes: summary.worked_minutes,
    base_amount: summary.earned_estimate,
    tips_amount: payroll.tips_amount,
    bonus_amount: payroll.bonus_amount,
    other_amount: payroll.other_amount,
    final_amount: finalAmount,
    status: "draft",
  });

  return findPayroll(employeeId, period)!;
}

export function markPaid(employeeId: number, period: string): Payroll {
  const payroll = findPayroll(employeeId, period);
  if (!payroll || payroll.status !== "ready") {
    throw new Error("Mzda musí být nejdřív uzamčena.");
  }

  db.prepare(
    "UPDATE payroll SET status = 'paid', payment_date = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ?"
  ).run(todayIso(), payroll.id);

  return findPayroll(employeeId, period)!;
}

export interface PayrollOverviewRow {
  employee_id: number;
  employee_name: string;
  closure_status: ClosureStatus;
  worked_minutes: number;
  base_amount: number;
  tips_amount: number;
  bonus_amount: number;
  other_amount: number;
  final_amount: number;
  payroll_status: PayrollStatus | null;
  payment_date: string | null;
}

export function getPeriodOverview(period: string): PayrollOverviewRow[] {
  return listEmployees()
    .filter((employee) => employee.active === 1)
    .map((employee) => {
      const closure = findClosure(employee.id, period);
      const payroll = findPayroll(employee.id, period);
      const summary = payroll ? null : getClosureSummary(employee.id, period);

      return {
        employee_id: employee.id,
        employee_name: employee.name ?? employee.email,
        closure_status: closure?.status ?? "waiting_employee",
        worked_minutes: payroll?.worked_minutes ?? summary!.worked_minutes,
        base_amount: payroll?.base_amount ?? summary!.earned_estimate,
        tips_amount: payroll?.tips_amount ?? 0,
        bonus_amount: payroll?.bonus_amount ?? 0,
        other_amount: payroll?.other_amount ?? 0,
        final_amount: payroll?.final_amount ?? summary!.earned_estimate,
        payroll_status: payroll?.status ?? null,
        payment_date: payroll?.payment_date ?? null,
      };
    });
}

function upsertPayroll(
  employeeId: number,
  period: string,
  data: {
    worked_minutes: number;
    base_amount: number;
    tips_amount: number;
    bonus_amount: number;
    other_amount: number;
    final_amount: number;
    status: PayrollStatus;
  }
): void {
  db.prepare(
    `INSERT INTO payroll (employee_id, period, worked_minutes, base_amount, tips_amount, bonus_amount, other_amount, final_amount, status, updated_at)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
     ON CONFLICT (employee_id, period) DO UPDATE SET
       worked_minutes = excluded.worked_minutes,
       base_amount = excluded.base_amount,
       tips_amount = excluded.tips_amount,
       bonus_amount = excluded.bonus_amount,
       other_amount = excluded.other_amount,
       final_amount = excluded.final_amount,
       status = excluded.status,
       updated_at = excluded.updated_at`
  ).run(
    employeeId,
    period,
    data.worked_minutes,
    data.base_amount,
    data.tips_amount,
    data.bonus_amount,
    data.other_amount,
    data.final_amount,
    data.status
  );
}
