import { accounting } from "@/lib/accountingClient";
import { debitMatchesBill, SETTLEABLE_BILL_STATUSES, type BillMatchInput } from "./billMatch";

export { debitMatchesBill, BILL_MATCH_WINDOW_DAYS, type BillMatchInput } from "./billMatch";

export type AutoBillSettings = {
  enabled: boolean;
  prompt_on_match: boolean;
  date_window_days: number;
  amount_tolerance_pct: number;
};

const DEFAULTS: AutoBillSettings = {
  enabled: true,
  prompt_on_match: true,
  date_window_days: 60,
  amount_tolerance_pct: 5,
};

export async function getAutoBillSettings(companyId: string): Promise<AutoBillSettings> {
  const { data } = await accounting
    .from("auto_bill_settings")
    .select("*")
    .eq("company_id", companyId)
    .maybeSingle();
  if (!data) return DEFAULTS;
  return {
    enabled: data.enabled,
    prompt_on_match: data.prompt_on_match,
    date_window_days: data.date_window_days,
    amount_tolerance_pct: data.amount_tolerance_pct,
  };
}

export async function upsertAutoBillSettings(companyId: string, s: Partial<AutoBillSettings>) {
  const current = await getAutoBillSettings(companyId);
  const merged = { ...current, ...s, company_id: companyId };
  const { data: existing } = await accounting
    .from("auto_bill_settings")
    .select("id")
    .eq("company_id", companyId)
    .maybeSingle();
  if (existing) {
    return accounting.from("auto_bill_settings").update(merged).eq("id", existing.id);
  }
  return accounting.from("auto_bill_settings").insert(merged);
}

export type PaymentPayload = {
  companyId: string;
  vendorId: string | null;
  amount: number;
  date: string; // YYYY-MM-DD
  category?: string | null;
  description?: string | null;
  /** Expense account to book the auto-created bill line against (falls back to the company default). */
  expenseAccountId?: string | null;
  sourceKind: "expense" | "check" | "transaction" | "journal";
  sourceId: string;
};

export async function findMatchingBills(p: PaymentPayload, s: AutoBillSettings) {
  if (!p.vendorId) return [];
  const start = new Date(p.date);
  start.setDate(start.getDate() - s.date_window_days);
  const end = new Date(p.date);
  end.setDate(end.getDate() + s.date_window_days);
  const { data } = await accounting
    .from("bills")
    .select("id, number, total, paid_amount, issue_date, status, vendor_id")
    .eq("company_id", p.companyId)
    .eq("vendor_id", p.vendorId)
    .gte("issue_date", start.toISOString().slice(0, 10))
    .lte("issue_date", end.toISOString().slice(0, 10))
    .in("status", ["open", "draft"]);
  const tol = s.amount_tolerance_pct / 100;
  return (data ?? []).filter((b: any) => {
    const balance = Number(b.total) - Number(b.paid_amount ?? 0);
    if (balance <= 0) return false;
    const diff = Math.abs(balance - p.amount);
    return diff <= Math.max(balance, p.amount) * tol;
  });
}

async function nextAutoNumber(companyId: string): Promise<string> {
  const { count } = await accounting
    .from("bills")
    .select("id", { count: "exact", head: true })
    .eq("company_id", companyId)
    .eq("auto_created", true);
  return `AUTO-BILL-${String((count ?? 0) + 1).padStart(4, "0")}`;
}

export async function createAutoBill(p: PaymentPayload): Promise<{ id: string; number: string } | null> {
  if (!p.vendorId) return null;
  const number = await nextAutoNumber(p.companyId);
  const { data: bill, error } = await accounting
    .from("bills")
    .insert({
      company_id: p.companyId,
      vendor_id: p.vendorId,
      number,
      issue_date: p.date,
      due_date: p.date,
      subtotal: p.amount,
      tax: 0,
      total: p.amount,
      paid_amount: p.amount,
      status: "paid",
      notes: `Auto-created from ${p.sourceKind} entry`,
      auto_created: true,
      source_payment_id: p.sourceId,
      source_payment_kind: p.sourceKind,
    })
    .select("id, number")
    .single();
  if (error || !bill) return null;
  await accounting.from("bill_items").insert({
    bill_id: bill.id,
    description: p.description || p.category || "Auto-created payment line",
    quantity: 1,
    rate: p.amount,
    amount: p.amount,
    account_id: p.expenseAccountId ?? null,
  });
  return bill;
}

export async function applyPaymentToBill(billId: string, amount: number) {
  const { data: bill } = await accounting.from("bills").select("total, paid_amount").eq("id", billId).single();
  if (!bill) return;
  const newPaid = Number(bill.paid_amount ?? 0) + amount;
  const total = Number(bill.total);
  // Fully paid → "paid"; some balance still owed after a payment → "partially_paid".
  const status = newPaid >= total - 0.005 ? "paid" : "partially_paid";
  await accounting.from("bills").update({ paid_amount: newPaid, status }).eq("id", billId);
}

/**
 * Find the open vendor bill(s) a bank debit settles, per the accrual matching rule:
 *   • same vendor, status not in (paid, void, draft)
 *   • debit date within ±30 days of the bill's due date (falling back to issue date)
 *   • remaining balance equals the debit within $0.01 (full settlement), or the
 *     debit is ≤ the remaining balance (partial payment)
 * Returns candidates oldest-first. Empty → treat the debit as a direct expense.
 * One candidate → apply automatically. More than one → caller should disambiguate.
 */
export async function matchOpenBills(input: BillMatchInput) {
  if (!input.vendorId || !(input.amount > 0)) return [];
  const { data } = await accounting
    .from("bills")
    .select("id, number, total, paid_amount, issue_date, due_date, status, vendor_id")
    .eq("company_id", input.companyId)
    .eq("vendor_id", input.vendorId)
    .in("status", SETTLEABLE_BILL_STATUSES);
  return (data ?? [])
    .filter((b: any) => debitMatchesBill(b, input))
    .sort((a: any, b: any) => String(a.issue_date ?? "").localeCompare(String(b.issue_date ?? "")));
}

/** Orchestrator: returns either a match prompt, an auto-created bill, or skip */
export type HandleResult =
  | { kind: "skipped"; reason: string }
  | { kind: "matched"; candidates: any[] }
  | { kind: "created"; bill: { id: string; number: string } };

export async function handlePayment(p: PaymentPayload): Promise<HandleResult> {
  if (!p.vendorId) return { kind: "skipped", reason: "no_vendor" };
  const settings = await getAutoBillSettings(p.companyId);
  if (!settings.enabled) return { kind: "skipped", reason: "disabled" };
  const candidates = await findMatchingBills(p, settings);
  if (candidates.length > 0 && settings.prompt_on_match) {
    return { kind: "matched", candidates };
  }
  const bill = await createAutoBill(p);
  if (!bill) return { kind: "skipped", reason: "create_failed" };
  return { kind: "created", bill };
}
