import type { ChartOfAccount, JournalEntry, JournalEntryLine } from '@/lib/types';

export type FinancialAccountCategory = 'assets' | 'liabilities' | 'equity' | 'revenues' | 'expenses' | 'unknown';
export type FinancialEntryStatus = 'all' | 'posted' | 'draft';

export type FinancialReportAccount = Pick<ChartOfAccount, 'id' | 'code' | 'nameAr' | 'nameEn' | 'type' | 'parentId' | 'nature' | 'inactive'>;
export type FinancialReportJournalEntry = Pick<JournalEntry, 'id' | 'date' | 'description' | 'status' | 'referenceType' | 'referenceId' | 'createdBy'> & {
  lines: Array<Pick<JournalEntryLine, 'accountId' | 'accountCode' | 'accountName' | 'debit' | 'credit' | 'description'>>;
};

export type TrialBalanceRow = {
  key: string;
  accountId: string;
  accountCode: string;
  accountName: string;
  category: FinancialAccountCategory;
  nature: 'debit' | 'credit';
  debitTotal: number;
  creditTotal: number;
  balanceDebit: number;
  balanceCredit: number;
  normalBalance: number;
};

export type LedgerRow = {
  entryId: string;
  date: string;
  description: string;
  referenceType: string;
  referenceId: string;
  debit: number;
  credit: number;
  runningDebit: number;
  runningCredit: number;
  runningNormalBalance: number;
};

export type StatementRow = {
  key: string;
  accountId: string;
  accountCode: string;
  accountName: string;
  category: FinancialAccountCategory;
  nature: 'debit' | 'credit';
  amount: number;
  displaySide: 'debit' | 'credit';
};

export type CashBankRow = {
  key: string;
  entryId: string;
  date: string;
  accountCode: string;
  accountName: string;
  description: string;
  referenceType: string;
  referenceId: string;
  counterparty: string;
  debit: number;
  credit: number;
  movement: number;
  runningBalance: number;
};

export type CashFlowRow = {
  key: string;
  entryId: string;
  date: string;
  description: string;
  referenceType: string;
  referenceId: string;
  category: 'operating' | 'investing' | 'financing';
  inflow: number;
  outflow: number;
  net: number;
};

type AccountIndex = {
  byId: Map<string, FinancialReportAccount>;
  byCode: Map<string, FinancialReportAccount>;
  byKey: Map<string, FinancialReportAccount>;
};

type EntryFilters = {
  fromDate?: string;
  toDate?: string;
  status?: FinancialEntryStatus;
};

function normalizeDate(value?: string): string {
  if (!value) return '';
  return String(value).trim().slice(0, 10);
}

function sortByDateThenCode<T extends { date?: string; accountCode?: string; entryId?: string }>(left: T, right: T) {
  const dateDiff = normalizeDate(left.date).localeCompare(normalizeDate(right.date));
  if (dateDiff !== 0) return dateDiff;
  const codeDiff = String(left.accountCode || '').localeCompare(String(right.accountCode || ''), 'en', { numeric: true });
  if (codeDiff !== 0) return codeDiff;
  return String(left.entryId || '').localeCompare(String(right.entryId || ''), 'en', { numeric: true });
}

function entryStatus(entry: FinancialReportJournalEntry): 'posted' | 'draft' {
  return String(entry.status || 'posted').trim().toLowerCase() === 'draft' ? 'draft' : 'posted';
}

export function matchesEntryFilters(entry: FinancialReportJournalEntry, filters: EntryFilters): boolean {
  const date = normalizeDate(entry.date);
  const fromDate = normalizeDate(filters.fromDate);
  const toDate = normalizeDate(filters.toDate);
  const status = filters.status || 'all';

  if (fromDate && date && date < fromDate) return false;
  if (toDate && date && date > toDate) return false;
  if (status !== 'all' && entryStatus(entry) !== status) return false;
  return true;
}

function makeAccountKey(accountId?: string, accountCode?: string) {
  return `${String(accountId || '').trim()}::${String(accountCode || '').trim()}`;
}

function buildAccountIndex(accounts: FinancialReportAccount[]): AccountIndex {
  const byId = new Map<string, FinancialReportAccount>();
  const byCode = new Map<string, FinancialReportAccount>();
  const byKey = new Map<string, FinancialReportAccount>();

  accounts.forEach((account) => {
    const id = String(account.id || '').trim();
    const code = String(account.code || '').trim();
    if (id) byId.set(id, account);
    if (code) byCode.set(code, account);
    byKey.set(makeAccountKey(id, code), account);
  });

  return { byId, byCode, byKey };
}

function resolveAccount(index: AccountIndex, line: FinancialReportJournalEntry['lines'][number]): FinancialReportAccount {
  const accountId = String(line.accountId || '').trim();
  const accountCode = String(line.accountCode || '').trim();
  const found = index.byKey.get(makeAccountKey(accountId, accountCode)) || index.byId.get(accountId) || index.byCode.get(accountCode);
  if (found) return found;

  return {
    id: accountId || accountCode,
    code: accountCode || accountId || '-',
    nameAr: String(line.accountName || accountCode || accountId || 'غير معروف'),
    nameEn: String(line.accountName || accountCode || accountId || 'Unknown'),
    type: 'account',
    parentId: null,
    nature: String(accountCode).startsWith('2') || String(accountCode).startsWith('3') || String(accountCode).startsWith('4') ? 'credit' : 'debit',
  };
}

export function classifyAccount(account: Pick<FinancialReportAccount, 'code'>): FinancialAccountCategory {
  const code = String(account.code || '').trim();
  if (code.startsWith('1')) return 'assets';
  if (code.startsWith('2')) return 'liabilities';
  if (code.startsWith('3')) return 'equity';
  if (code.startsWith('4')) return 'revenues';
  if (code.startsWith('5')) return 'expenses';
  return 'unknown';
}

function computeNormalBalance(nature: 'debit' | 'credit', debitTotal: number, creditTotal: number) {
  return nature === 'credit' ? (creditTotal - debitTotal) : (debitTotal - creditTotal);
}

function normalBalanceToSides(nature: 'debit' | 'credit', normalBalance: number) {
  if (nature === 'debit') {
    return {
      balanceDebit: Math.max(normalBalance, 0),
      balanceCredit: Math.max(-normalBalance, 0),
    };
  }

  return {
    balanceDebit: Math.max(-normalBalance, 0),
    balanceCredit: Math.max(normalBalance, 0),
  };
}

export function buildTrialBalanceRows(entries: FinancialReportJournalEntry[], accounts: FinancialReportAccount[], filters: EntryFilters): TrialBalanceRow[] {
  const index = buildAccountIndex(accounts);
  const totals = new Map<string, TrialBalanceRow>();

  entries.filter((entry) => matchesEntryFilters(entry, filters)).forEach((entry) => {
    (entry.lines || []).forEach((line) => {
      const account = resolveAccount(index, line);
      const key = makeAccountKey(account.id, account.code);
      const current = totals.get(key) || {
        key,
        accountId: String(account.id || '').trim() || String(account.code || '').trim(),
        accountCode: String(account.code || '').trim() || '-',
        accountName: String(account.nameAr || account.nameEn || line.accountName || '-').trim(),
        category: classifyAccount(account),
        nature: account.nature === 'credit' ? 'credit' : 'debit',
        debitTotal: 0,
        creditTotal: 0,
        balanceDebit: 0,
        balanceCredit: 0,
        normalBalance: 0,
      };
      current.debitTotal += Number(line.debit || 0);
      current.creditTotal += Number(line.credit || 0);
      totals.set(key, current);
    });
  });

  return Array.from(totals.values())
    .map((row) => {
      const normalBalance = computeNormalBalance(row.nature, row.debitTotal, row.creditTotal);
      const sides = normalBalanceToSides(row.nature, normalBalance);
      return {
        ...row,
        normalBalance,
        ...sides,
      };
    })
    .filter((row) => Math.abs(row.debitTotal) > 0.0001 || Math.abs(row.creditTotal) > 0.0001)
    .sort((left, right) => left.accountCode.localeCompare(right.accountCode, 'en', { numeric: true }));
}

export function buildLedgerRows(entries: FinancialReportJournalEntry[], accounts: FinancialReportAccount[], accountKey: string, filters: EntryFilters): LedgerRow[] {
  if (!accountKey) return [];
  const index = buildAccountIndex(accounts);
  const selectedAccount = index.byKey.get(accountKey) || index.byId.get(accountKey) || index.byCode.get(accountKey);
  if (!selectedAccount) return [];

  const rows: LedgerRow[] = [];
  let runningNormalBalance = 0;

  entries
    .filter((entry) => matchesEntryFilters(entry, filters))
    .sort((left, right) => sortByDateThenCode({ date: left.date, entryId: left.id }, { date: right.date, entryId: right.id }))
    .forEach((entry) => {
      (entry.lines || []).forEach((line) => {
        const account = resolveAccount(index, line);
        if (makeAccountKey(account.id, account.code) !== makeAccountKey(selectedAccount.id, selectedAccount.code)) return;

        const debit = Number(line.debit || 0);
        const credit = Number(line.credit || 0);
        runningNormalBalance += selectedAccount.nature === 'credit' ? (credit - debit) : (debit - credit);
        const runningSides = normalBalanceToSides(selectedAccount.nature === 'credit' ? 'credit' : 'debit', runningNormalBalance);

        rows.push({
          entryId: String(entry.id || '').trim(),
          date: normalizeDate(entry.date),
          description: [String(entry.description || '').trim(), String(line.description || '').trim()].filter(Boolean).join(' - '),
          referenceType: String(entry.referenceType || '').trim(),
          referenceId: String(entry.referenceId || '').trim(),
          debit,
          credit,
          runningDebit: runningSides.balanceDebit,
          runningCredit: runningSides.balanceCredit,
          runningNormalBalance,
        });
      });
    });

  return rows;
}

export function buildBalanceSheetData(entries: FinancialReportJournalEntry[], accounts: FinancialReportAccount[], filters: Omit<EntryFilters, 'fromDate'> & { toDate?: string }) {
  const rows = buildTrialBalanceRows(entries, accounts, { status: filters.status, toDate: filters.toDate });
  const assets = rows.filter((row) => row.category === 'assets').map((row) => ({
    key: row.key,
    accountId: row.accountId,
    accountCode: row.accountCode,
    accountName: row.accountName,
    category: row.category,
    nature: row.nature,
    amount: row.balanceDebit > 0 ? row.balanceDebit : row.balanceCredit,
    displaySide: row.balanceDebit > 0 ? 'debit' as const : 'credit' as const,
  }));
  const liabilities = rows.filter((row) => row.category === 'liabilities').map((row) => ({
    key: row.key,
    accountId: row.accountId,
    accountCode: row.accountCode,
    accountName: row.accountName,
    category: row.category,
    nature: row.nature,
    amount: row.balanceDebit > 0 ? row.balanceDebit : row.balanceCredit,
    displaySide: row.balanceDebit > 0 ? 'debit' as const : 'credit' as const,
  }));
  const equity = rows.filter((row) => row.category === 'equity').map((row) => ({
    key: row.key,
    accountId: row.accountId,
    accountCode: row.accountCode,
    accountName: row.accountName,
    category: row.category,
    nature: row.nature,
    amount: row.balanceDebit > 0 ? row.balanceDebit : row.balanceCredit,
    displaySide: row.balanceDebit > 0 ? 'debit' as const : 'credit' as const,
  }));

  const incomeStatement = buildIncomeStatementData(entries, accounts, { toDate: filters.toDate, status: filters.status });
  if (Math.abs(incomeStatement.netIncome) > 0.0001) {
    equity.push({
      key: 'derived-net-income',
      accountId: 'derived-net-income',
      accountCode: '3140*',
      accountName: 'صافي الفترة',
      category: 'equity',
      nature: 'credit',
      amount: Math.abs(incomeStatement.netIncome),
      displaySide: incomeStatement.netIncome >= 0 ? 'credit' : 'debit',
    });
  }

  const sumAmounts = (rowsToSum: StatementRow[]) => rowsToSum.reduce((sum, row) => sum + Number(row.amount || 0), 0);
  const assetTotal = sumAmounts(assets);
  const liabilitiesTotal = sumAmounts(liabilities);
  const equityTotal = sumAmounts(equity);

  return {
    assets,
    liabilities,
    equity,
    assetTotal,
    liabilitiesTotal,
    equityTotal,
    liabilitiesAndEquityTotal: liabilitiesTotal + equityTotal,
    difference: assetTotal - (liabilitiesTotal + equityTotal),
  };
}

export function buildIncomeStatementData(entries: FinancialReportJournalEntry[], accounts: FinancialReportAccount[], filters: EntryFilters) {
  const rows = buildTrialBalanceRows(entries, accounts, filters);
  const revenues = rows
    .filter((row) => row.category === 'revenues')
    .map((row) => ({
      key: row.key,
      accountId: row.accountId,
      accountCode: row.accountCode,
      accountName: row.accountName,
      category: row.category,
      nature: row.nature,
      amount: row.balanceCredit > 0 ? row.balanceCredit : row.balanceDebit,
      displaySide: row.balanceCredit > 0 ? 'credit' as const : 'debit' as const,
    }));
  const expenses = rows
    .filter((row) => row.category === 'expenses')
    .map((row) => ({
      key: row.key,
      accountId: row.accountId,
      accountCode: row.accountCode,
      accountName: row.accountName,
      category: row.category,
      nature: row.nature,
      amount: row.balanceDebit > 0 ? row.balanceDebit : row.balanceCredit,
      displaySide: row.balanceDebit > 0 ? 'debit' as const : 'credit' as const,
    }));

  const revenueTotal = revenues.reduce((sum, row) => sum + Number(row.amount || 0), 0);
  const expenseTotal = expenses.reduce((sum, row) => sum + Number(row.amount || 0), 0);

  return {
    revenues,
    expenses,
    revenueTotal,
    expenseTotal,
    netIncome: revenueTotal - expenseTotal,
  };
}

function isCashOrBankAccount(account: FinancialReportAccount) {
  const code = String(account.code || '').trim();
  const name = `${String(account.nameAr || '').trim()} ${String(account.nameEn || '').trim()}`;
  return code.startsWith('111') || code.startsWith('112') || /الصندوق|البنك|cash|bank/i.test(name);
}

function buildCounterpartySummary(entry: FinancialReportJournalEntry, currentAccountKey: string, index: AccountIndex) {
  const names = (entry.lines || [])
    .map((line) => resolveAccount(index, line))
    .filter((account) => makeAccountKey(account.id, account.code) !== currentAccountKey)
    .map((account) => String(account.nameAr || account.nameEn || account.code || '').trim())
    .filter(Boolean);

  return Array.from(new Set(names)).slice(0, 3).join('، ');
}

export function buildCashBankRows(entries: FinancialReportJournalEntry[], accounts: FinancialReportAccount[], filters: EntryFilters): CashBankRow[] {
  const index = buildAccountIndex(accounts);
  const rows: CashBankRow[] = [];
  const runningByAccount = new Map<string, number>();

  entries
    .filter((entry) => matchesEntryFilters(entry, filters))
    .sort((left, right) => sortByDateThenCode({ date: left.date, entryId: left.id }, { date: right.date, entryId: right.id }))
    .forEach((entry) => {
      (entry.lines || []).forEach((line, lineIndex) => {
        const account = resolveAccount(index, line);
        if (!isCashOrBankAccount(account)) return;

        const key = makeAccountKey(account.id, account.code);
        const movement = Number(line.debit || 0) - Number(line.credit || 0);
        const nextRunning = (runningByAccount.get(key) || 0) + movement;
        runningByAccount.set(key, nextRunning);

        rows.push({
          key: `${entry.id}-${key}-${lineIndex}`,
          entryId: String(entry.id || '').trim(),
          date: normalizeDate(entry.date),
          accountCode: String(account.code || '').trim(),
          accountName: String(account.nameAr || account.nameEn || line.accountName || '').trim(),
          description: String(entry.description || '').trim(),
          referenceType: String(entry.referenceType || '').trim(),
          referenceId: String(entry.referenceId || '').trim(),
          counterparty: buildCounterpartySummary(entry, key, index),
          debit: Number(line.debit || 0),
          credit: Number(line.credit || 0),
          movement,
          runningBalance: nextRunning,
        });
      });
    });

  return rows.sort((left, right) => sortByDateThenCode(left, right));
}

function deriveCashFlowCategory(entry: FinancialReportJournalEntry, index: AccountIndex): 'operating' | 'investing' | 'financing' {
  const nonCashCategories = (entry.lines || [])
    .map((line) => resolveAccount(index, line))
    .filter((account) => !isCashOrBankAccount(account))
    .map((account) => ({ category: classifyAccount(account), code: String(account.code || '').trim() }));

  if (nonCashCategories.some((entry) => entry.category === 'assets' && !entry.code.startsWith('11'))) {
    return 'investing';
  }
  if (nonCashCategories.some((entry) => entry.category === 'liabilities' || entry.category === 'equity')) {
    return 'financing';
  }
  return 'operating';
}

export function buildCashFlowRows(entries: FinancialReportJournalEntry[], accounts: FinancialReportAccount[], filters: EntryFilters): CashFlowRow[] {
  const index = buildAccountIndex(accounts);

  return entries
    .filter((entry) => matchesEntryFilters(entry, filters))
    .map((entry) => {
      const cashNet = (entry.lines || []).reduce((sum, line) => {
        const account = resolveAccount(index, line);
        if (!isCashOrBankAccount(account)) return sum;
        return sum + Number(line.debit || 0) - Number(line.credit || 0);
      }, 0);

      if (Math.abs(cashNet) < 0.0001) return null;

      return {
        key: String(entry.id || '').trim(),
        entryId: String(entry.id || '').trim(),
        date: normalizeDate(entry.date),
        description: String(entry.description || '').trim(),
        referenceType: String(entry.referenceType || '').trim(),
        referenceId: String(entry.referenceId || '').trim(),
        category: deriveCashFlowCategory(entry, index),
        inflow: cashNet > 0 ? cashNet : 0,
        outflow: cashNet < 0 ? Math.abs(cashNet) : 0,
        net: cashNet,
      };
    })
    .filter((row): row is CashFlowRow => Boolean(row))
    .sort((left, right) => left.date.localeCompare(right.date) || left.entryId.localeCompare(right.entryId, 'en', { numeric: true }));
}
