import "server-only";

import { Query } from "node-appwrite";

import { createAdminClient } from "./server";
import {
  DATABASE_ID,
  TABLES,
  type BankAccount,
  type BankLoan,
  type CreditCard,
  type CreditCardStatement,
  type Customer,
  type FinanceEntry,
  type Invoice,
  type LoanInstallment,
} from "./schema";

export type ReportPeriod = "month" | "quarter" | "year" | "all";

export type FinancialReport = {
  period: ReportPeriod;
  periodStart: string | null;
  periodEnd: string | null;
  // KPIs
  kpi: {
    income: number;
    expense: number;
    net: number;
    cashPosition: number; // bank balances + receivables - loan remaining - card outstanding
  };
  // Cash composition (right now, not period-bound)
  composition: {
    bankBalances: number;
    receivables: number; // unpaid invoices total (not paid, not cancelled)
    loanRemaining: number;
    cardOutstanding: number;
  };
  // Trend (last 12 months income/expense)
  trend: { month: string; income: number; expense: number; net: number }[];
  // Top customers by paid invoice total (period-bound when period != all)
  topCustomers: { name: string; total: number }[];
  // Top expense buckets — auto-grouped by source
  expenseBreakdown: {
    invoices: number; // expenses linked to invoices? we don't track AP invoices yet — leave 0
    loans: number; // finance_entries linked through loan installments (description match heuristic)
    cards: number;
    other: number;
  };
  // Active loans
  loans: {
    id: string;
    bankName: string;
    loanName: string;
    principal: number;
    remaining: number;
    monthlyPayment: number;
    nextDue: string | null;
  }[];
  // Credit card outstanding statements
  cardStatements: {
    id: string;
    cardLabel: string;
    period: string;
    dueDate: string;
    remaining: number;
    status: "pending" | "partial" | "overdue";
  }[];
  // Outstanding (unpaid) invoices
  outstandingInvoices: {
    id: string;
    number: string;
    customerName: string;
    dueDate: string;
    total: number;
    overdue: boolean;
  }[];
};

const MONTH_SHORT = ["Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara"];

function periodBounds(p: ReportPeriod): { start: Date | null; end: Date | null } {
  const now = new Date();
  if (p === "month") {
    return {
      start: new Date(now.getFullYear(), now.getMonth(), 1),
      end: new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59),
    };
  }
  if (p === "quarter") {
    const q = Math.floor(now.getMonth() / 3);
    return {
      start: new Date(now.getFullYear(), q * 3, 1),
      end: new Date(now.getFullYear(), q * 3 + 3, 0, 23, 59, 59),
    };
  }
  if (p === "year") {
    return {
      start: new Date(now.getFullYear(), 0, 1),
      end: new Date(now.getFullYear(), 11, 31, 23, 59, 59),
    };
  }
  return { start: null, end: null };
}

function inRange(iso: string, start: Date | null, end: Date | null): boolean {
  if (!start || !end) return true;
  const t = new Date(iso).getTime();
  return t >= start.getTime() && t <= end.getTime();
}

export async function getFinancialReport(
  tenantId: string,
  period: ReportPeriod = "month",
): Promise<FinancialReport> {
  const { tablesDB } = createAdminClient();

  const [
    customers,
    invoices,
    finance,
    bankAccounts,
    loans,
    installments,
    cards,
    statements,
  ] = await Promise.all([
    tablesDB
      .listRows({
        databaseId: DATABASE_ID,
        tableId: TABLES.customers,
        queries: [Query.equal("tenantId", tenantId), Query.limit(2000)],
      })
      .catch(() => ({ rows: [] as unknown[] })),
    tablesDB
      .listRows({
        databaseId: DATABASE_ID,
        tableId: TABLES.invoices,
        queries: [Query.equal("tenantId", tenantId), Query.limit(2000)],
      })
      .catch(() => ({ rows: [] as unknown[] })),
    tablesDB
      .listRows({
        databaseId: DATABASE_ID,
        tableId: TABLES.financeEntries,
        queries: [Query.equal("tenantId", tenantId), Query.limit(5000)],
      })
      .catch(() => ({ rows: [] as unknown[] })),
    tablesDB
      .listRows({
        databaseId: DATABASE_ID,
        tableId: TABLES.bankAccounts,
        queries: [Query.equal("tenantId", tenantId), Query.limit(200)],
      })
      .catch(() => ({ rows: [] as unknown[] })),
    tablesDB
      .listRows({
        databaseId: DATABASE_ID,
        tableId: TABLES.bankLoans,
        queries: [Query.equal("tenantId", tenantId), Query.limit(200)],
      })
      .catch(() => ({ rows: [] as unknown[] })),
    tablesDB
      .listRows({
        databaseId: DATABASE_ID,
        tableId: TABLES.loanInstallments,
        queries: [Query.equal("tenantId", tenantId), Query.limit(5000)],
      })
      .catch(() => ({ rows: [] as unknown[] })),
    tablesDB
      .listRows({
        databaseId: DATABASE_ID,
        tableId: TABLES.creditCards,
        queries: [Query.equal("tenantId", tenantId), Query.limit(200)],
      })
      .catch(() => ({ rows: [] as unknown[] })),
    tablesDB
      .listRows({
        databaseId: DATABASE_ID,
        tableId: TABLES.creditCardStatements,
        queries: [Query.equal("tenantId", tenantId), Query.limit(2000)],
      })
      .catch(() => ({ rows: [] as unknown[] })),
  ]);

  // Reports reflect COMPANY finances only — personal-scope entities are
  // private to their creator and must not flow into team-level metrics.
  const isCompany = <T extends { scope?: "company" | "personal" }>(r: T) =>
    (r.scope ?? "company") === "company";

  const customerList = customers.rows as unknown as Customer[];
  const invoiceList = invoices.rows as unknown as Invoice[];
  const entryList = (finance.rows as unknown as FinanceEntry[]).filter(isCompany);
  const bankList = (bankAccounts.rows as unknown as BankAccount[]).filter(isCompany);
  const loanList = (loans.rows as unknown as BankLoan[]).filter(isCompany);
  const cardList = (cards.rows as unknown as CreditCard[]).filter(isCompany);
  const visibleLoanIds = new Set(loanList.map((l) => l.$id));
  const visibleCardIds = new Set(cardList.map((c) => c.$id));
  const installmentList = (installments.rows as unknown as LoanInstallment[]).filter(
    (i) => visibleLoanIds.has(i.loanId),
  );
  const statementList = (statements.rows as unknown as CreditCardStatement[]).filter(
    (s) => visibleCardIds.has(s.cardId),
  );

  const customerMap = new Map(customerList.map((c) => [c.$id, c.name]));
  const cardMap = new Map(
    cardList.map((c) => [c.$id, `${c.bankName} — ${c.cardName}${c.last4 ? ` **${c.last4}` : ""}`]),
  );

  const { start, end } = periodBounds(period);

  // ---------- KPIs (period-bound for income/expense, current for cash) ----------
  let income = 0;
  let expense = 0;
  for (const e of entryList) {
    if (!inRange(e.date, start, end)) continue;
    if (e.type === "income") income += e.amount;
    else if (e.type === "expense") expense += e.amount;
  }

  // bank balance (today, not period-bound)
  const balances = new Map<string, number>();
  for (const a of bankList) balances.set(a.$id, a.openingBalance ?? 0);
  for (const e of entryList) {
    if (!e.bankAccountId) continue;
    const cur = balances.get(e.bankAccountId);
    if (cur === undefined) continue;
    if (e.type === "income") balances.set(e.bankAccountId, cur + e.amount);
    else if (e.type === "expense") balances.set(e.bankAccountId, cur - e.amount);
  }
  const bankBalances = Array.from(balances.values()).reduce((s, n) => s + n, 0);

  // receivables = sum of unpaid invoices
  const receivables = invoiceList.reduce((s, inv) => {
    const st = inv.status ?? "draft";
    if (st === "paid" || st === "cancelled") return s;
    return s + (inv.total ?? 0);
  }, 0);

  // loan remaining = sum of unpaid installments
  const loanRemaining = installmentList.reduce(
    (s, i) => (i.paid ? s : s + (i.amount ?? 0)),
    0,
  );

  // card outstanding = sum of (totalDebt - paidAmount) for non-paid statements
  const cardOutstanding = statementList.reduce(
    (s, st) =>
      st.status === "paid" ? s : s + ((st.totalDebt ?? 0) - (st.paidAmount ?? 0)),
    0,
  );

  const cashPosition = bankBalances + receivables - loanRemaining - cardOutstanding;

  // ---------- Trend (last 12 months always) ----------
  const trend: FinancialReport["trend"] = [];
  const now = new Date();
  for (let i = 11; i >= 0; i--) {
    const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
    trend.push({
      month: MONTH_SHORT[d.getMonth()],
      income: 0,
      expense: 0,
      net: 0,
    });
  }
  for (const e of entryList) {
    const ed = new Date(e.date);
    const monthsAgo =
      (now.getFullYear() - ed.getFullYear()) * 12 + (now.getMonth() - ed.getMonth());
    if (monthsAgo < 0 || monthsAgo > 11) continue;
    const idx = 11 - monthsAgo;
    if (e.type === "income") trend[idx].income += e.amount;
    else if (e.type === "expense") trend[idx].expense += e.amount;
  }
  for (const t of trend) t.net = t.income - t.expense;

  // ---------- Top customers (period-bound paid invoices) ----------
  const customerRevenue = new Map<string, number>();
  for (const inv of invoiceList) {
    if (inv.status !== "paid") continue;
    if (start && !inRange(inv.issueDate, start, end)) continue;
    customerRevenue.set(
      inv.customerId,
      (customerRevenue.get(inv.customerId) ?? 0) + (inv.total ?? 0),
    );
  }
  const topCustomers = Array.from(customerRevenue.entries())
    .map(([id, total]) => ({ name: customerMap.get(id) ?? "—", total }))
    .sort((a, b) => b.total - a.total)
    .slice(0, 8);

  // ---------- Expense breakdown (period-bound) ----------
  let expLoans = 0;
  let expCards = 0;
  let expOther = 0;
  const installmentEntryIds = new Set(
    installmentList.map((i) => i.financeEntryId).filter(Boolean) as string[],
  );
  const statementEntryIds = new Set(
    statementList.map((s) => s.financeEntryId).filter(Boolean) as string[],
  );
  for (const e of entryList) {
    if (e.type !== "expense") continue;
    if (!inRange(e.date, start, end)) continue;
    if (installmentEntryIds.has(e.$id)) expLoans += e.amount;
    else if (statementEntryIds.has(e.$id)) expCards += e.amount;
    else expOther += e.amount;
  }

  // ---------- Active loans summary ----------
  const installmentsByLoan = new Map<string, LoanInstallment[]>();
  for (const i of installmentList) {
    const arr = installmentsByLoan.get(i.loanId) ?? [];
    arr.push(i);
    installmentsByLoan.set(i.loanId, arr);
  }
  const loansSummary = loanList
    .filter((l) => (l.status ?? "active") === "active")
    .map((l) => {
      const items = installmentsByLoan.get(l.$id) ?? [];
      const remaining = items.filter((i) => !i.paid).reduce((s, i) => s + i.amount, 0);
      const unpaid = items.filter((i) => !i.paid).sort(
        (a, b) => new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime(),
      );
      return {
        id: l.$id,
        bankName: l.bankName,
        loanName: l.loanName,
        principal: l.principal,
        remaining,
        monthlyPayment: l.monthlyPayment ?? 0,
        nextDue: unpaid[0]?.dueDate ?? null,
      };
    })
    .sort((a, b) => b.remaining - a.remaining);

  // ---------- Credit card outstanding statements ----------
  const cardStmts = statementList
    .filter((s) => s.status !== "paid")
    .map((s) => ({
      id: s.$id,
      cardLabel: cardMap.get(s.cardId) ?? "—",
      period: s.period,
      dueDate: s.dueDate,
      remaining: (s.totalDebt ?? 0) - (s.paidAmount ?? 0),
      status: (s.status ?? "pending") as "pending" | "partial" | "overdue",
    }))
    .sort((a, b) => new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime());

  // ---------- Outstanding invoices ----------
  const today = new Date();
  const outstandingInvoices = invoiceList
    .filter((inv) => {
      const st = inv.status ?? "draft";
      return st !== "paid" && st !== "cancelled";
    })
    .map((inv) => ({
      id: inv.$id,
      number: inv.number,
      customerName: customerMap.get(inv.customerId) ?? "—",
      dueDate: inv.dueDate,
      total: inv.total ?? 0,
      overdue: new Date(inv.dueDate) < today,
    }))
    .sort((a, b) => {
      if (a.overdue !== b.overdue) return a.overdue ? -1 : 1;
      return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime();
    })
    .slice(0, 12);

  return {
    period,
    periodStart: start ? start.toISOString() : null,
    periodEnd: end ? end.toISOString() : null,
    kpi: {
      income,
      expense,
      net: income - expense,
      cashPosition,
    },
    composition: {
      bankBalances,
      receivables,
      loanRemaining,
      cardOutstanding,
    },
    trend,
    topCustomers,
    expenseBreakdown: {
      invoices: 0,
      loans: expLoans,
      cards: expCards,
      other: expOther,
    },
    loans: loansSummary.slice(0, 8),
    cardStatements: cardStmts.slice(0, 12),
    outstandingInvoices,
  };
}
