/**
 * Default Sales Accounting Port Adapter
 * Creates journal entries for sales invoices
 * Uses the configured chart of accounts and persists one deterministic journal per invoice
 */

import { pgDeleteJournalEntry, pgGetChartOfAccounts, pgGetCustomers, pgGetInvoices, pgGetJournalEntries, pgGetMaterials, pgGetSuppliers, pgSaveJournalEntry } from '@/lib/postgres/data-access';
import type { ChartOfAccount, JournalEntry, JournalEntryLine } from '@/lib/types';
import type { SalesInvoicePaymentMethod } from '../domain/sales-invoice.model';
import { SalesAccountingPort, SalesInvoiceAccountingEntry } from '../application/sales-accounting.port';
import { resolveCoverageSplit } from '../shared/coverage-allocation';

const SALES_TAX_JOURNAL_ID_PREFIX = 'JE-SALES-INVOICE';
const SALES_SHIPMENT_DRAFT_JOURNAL_ID_PREFIX = 'JED-SALES-SHIPMENT';
const SALES_SHIPMENT_POSTED_JOURNAL_ID_PREFIX = 'JE-SALES-SHIPMENT';

function roundAmount(value: number): number {
  return Math.round((Number(value || 0) + Number.EPSILON) * 100) / 100;
}

function buildSalesTaxJournalId(invoiceId: string): string {
  return `${SALES_TAX_JOURNAL_ID_PREFIX}-${invoiceId}`;
}

function buildShipmentDraftJournalId(invoiceId: string): string {
  return `${SALES_SHIPMENT_DRAFT_JOURNAL_ID_PREFIX}-${invoiceId}`;
}

function buildShipmentPostedJournalId(invoiceId: string): string {
  return `${SALES_SHIPMENT_POSTED_JOURNAL_ID_PREFIX}-${invoiceId}`;
}

function resolveJournalId(entry: SalesInvoiceAccountingEntry): string {
  if (entry.documentType === 'shipment') {
    return entry.status === 'draft'
      ? buildShipmentDraftJournalId(entry.invoiceId)
      : buildShipmentPostedJournalId(entry.invoiceId);
  }
  return buildSalesTaxJournalId(entry.invoiceId);
}

export class DefaultSalesAccountingPort implements SalesAccountingPort {
  private async loadAccounts(): Promise<ChartOfAccount[]> {
    
    const result = await pgGetChartOfAccounts({ page: 1, pageSize: 5000 });
    return (result.items || []) as ChartOfAccount[];
  }

  private getRequiredAccount(accounts: ChartOfAccount[], code: string, label: string): ChartOfAccount {
    const account = accounts.find((entry) => entry.code === code && entry.type === 'account');
    if (!account) {
      throw new Error(`Missing chart-of-accounts mapping for ${label} (${code})`);
    }
    return account;
  }

  private pushLine(lines: JournalEntryLine[], nextLine: JournalEntryLine) {
    const debit = roundAmount(nextLine.debit);
    const credit = roundAmount(nextLine.credit);
    if (debit <= 0 && credit <= 0) return;

    const existing = lines.find((line) => line.accountCode === nextLine.accountCode && line.description === nextLine.description);
    if (existing) {
      existing.debit = roundAmount(existing.debit + debit);
      existing.credit = roundAmount(existing.credit + credit);
      return;
    }

    lines.push({
      ...nextLine,
      debit,
      credit,
    });
  }

  private normalizePaymentAmounts(
    paymentMethods: SalesInvoicePaymentMethod[] | undefined,
    exchangeRate: number
  ): Array<{ method: SalesInvoicePaymentMethod['method']; amount: number }> {
    return (paymentMethods || []).map((method) => ({
      method: method.method,
      amount: roundAmount(Number(method.amount || 0) * exchangeRate),
    })).filter((method) => method.amount > 0);
  }

  private async persistJournalEntry(entry: JournalEntry): Promise<void> {
    
    await pgSaveJournalEntry([entry as unknown as Record<string, unknown>]);
    return;
  }

  private async loadJournalEntryById(journalId: string): Promise<JournalEntry | null> {
    
    const result = await pgGetJournalEntries({ page: 1, pageSize: 5000 });
    const match = (result.items || []).find((entry: any) => String(entry.id || '') === journalId);
    return (match || null) as JournalEntry | null;
  }

  private async loadMaterials(): Promise<Array<Record<string, unknown>>> {
    
    const result = await pgGetMaterials({ page: 1, pageSize: 5000 });
    return (result.items || []) as Array<Record<string, unknown>>;
  }

  private async loadInvoice(invoiceId: string): Promise<Record<string, unknown> | null> {
    
    const result = await pgGetInvoices({ page: 1, pageSize: 5000 });
    const match = (result.items || []).find((entry: any) => String(entry?.id || '') === String(invoiceId || ''));
    return (match || null) as Record<string, unknown> | null;
  }

  private async resolveRevenueCategoryAmounts(invoiceId: string, grossSales: number): Promise<{ inventory: number; service: number; nonStock: number }> {
    const safeGross = roundAmount(Math.max(0, Number(grossSales || 0)));
    if (safeGross <= 0) {
      return { inventory: 0, service: 0, nonStock: 0 };
    }

    const invoice = await this.loadInvoice(invoiceId);
    const items = Array.isArray((invoice as any)?.items) ? ((invoice as any).items as Array<Record<string, unknown>>) : [];
    if (items.length === 0) {
      return { inventory: safeGross, service: 0, nonStock: 0 };
    }

    const materials = await this.loadMaterials();
    const materialById = new Map(materials.map((entry) => [String(entry.id || ''), entry]));
    const totals = { inventory: 0, service: 0, nonStock: 0 };

    for (const line of items) {
      const materialId = String(line.materialId || '').trim();
      const material = materialById.get(materialId);
      const category = String(material?.itemCategory || '').trim();
      const amount = Math.max(0, Number(line.total || (Number(line.quantity || 0) * Number(line.unitPrice || 0)) || 0));
      if (amount <= 0) continue;

      if (category === 'service') {
        totals.service += amount;
      } else if (category === 'non_stock') {
        totals.nonStock += amount;
      } else {
        totals.inventory += amount;
      }
    }

    const totalBase = totals.inventory + totals.service + totals.nonStock;
    if (totalBase <= 0) {
      return { inventory: safeGross, service: 0, nonStock: 0 };
    }

    const serviceAmount = roundAmount((totals.service / totalBase) * safeGross);
    const nonStockAmount = roundAmount((totals.nonStock / totalBase) * safeGross);
    const inventoryAmount = roundAmount(safeGross - serviceAmount - nonStockAmount);

    return {
      inventory: Math.max(0, inventoryAmount),
      service: Math.max(0, serviceAmount),
      nonStock: Math.max(0, nonStockAmount),
    };
  }

  private async resolveCustomerReceivablesAccount(
    accounts: ChartOfAccount[],
    customerId: string,
    customerName?: string
  ): Promise<ChartOfAccount> {
    const fallbackAccount = this.getRequiredAccount(accounts, '1130', 'Accounts Receivable');
    const normalizedCustomerId = String(customerId || '').trim();
    const normalizedCustomerName = String(customerName || '').trim();

    const resolveByCode = (code: string): ChartOfAccount | null => {
      const normalized = String(code || '').trim();
      if (!normalized) return null;
      return accounts.find((account) => account.code === normalized && account.type === 'account') || null;
    };

    let accountCode = '';
    
    const [customersResult, suppliersResult] = await Promise.all([
    pgGetCustomers({ page: 1, pageSize: 5000 }),
    pgGetSuppliers({ page: 1, pageSize: 5000 }),
    ]);
    const customers = (customersResult.items || []) as Array<Record<string, unknown>>;
    const suppliers = (suppliersResult.items || []) as Array<Record<string, unknown>>;

    const byCustomerId = normalizedCustomerId
    ? customers.find((entry) => String(entry.id || '').trim() === normalizedCustomerId)
    : undefined;
    accountCode = String((byCustomerId as any)?.accountCode || '').trim();

    if (!accountCode) {
    const bySupplierId = normalizedCustomerId
      ? suppliers.find((entry) => String(entry.id || '').trim() === normalizedCustomerId)
      : undefined;
    accountCode = String((bySupplierId as any)?.accountCode || '').trim();
    }

    if (!accountCode && normalizedCustomerName) {
    const byCustomerName = customers.find((entry) => String((entry as any)?.name || '').trim() === normalizedCustomerName);
    accountCode = String((byCustomerName as any)?.accountCode || '').trim();
    }

    if (!accountCode && normalizedCustomerName) {
    const bySupplierName = suppliers.find((entry) => String((entry as any)?.name || '').trim() === normalizedCustomerName);
    accountCode = String((bySupplierName as any)?.accountCode || '').trim();
    }
    

    const resolved = resolveByCode(accountCode);
    return resolved || fallbackAccount;
  }

  /**
   * Create accounting entry for a new invoice
   */
  async createInvoiceEntry(
    entry: SalesInvoiceAccountingEntry
  ): Promise<void> {
    const accounts = await this.loadAccounts();
    const cashAccount = this.getRequiredAccount(accounts, '1110', 'Cash');
    const bankAccount = this.getRequiredAccount(accounts, '1120', 'Bank');
    const receivablesAccount = await this.resolveCustomerReceivablesAccount(accounts, entry.customerId, entry.customerName);
    const normalizedSecondaryCustomerId = String(entry.secondaryCustomerId || '').trim();
    const normalizedSecondaryCustomerName = String(entry.secondaryCustomerName || '').trim();
    const secondaryCoveragePercent = Math.max(0, Math.min(100, Number(entry.secondaryCoveragePercent || 0)));
    const hasSecondaryCoverage = Boolean(normalizedSecondaryCustomerId) && secondaryCoveragePercent > 0;
    const secondaryReceivablesAccount = hasSecondaryCoverage
      ? await this.resolveCustomerReceivablesAccount(accounts, normalizedSecondaryCustomerId, normalizedSecondaryCustomerName)
      : null;
    const notesReceivableAccount = this.getRequiredAccount(accounts, '1140', 'Notes Receivable');
    const salesRevenueAccount = this.getRequiredAccount(accounts, '4110', 'Sales Revenue');
    const serviceRevenueAccount = accounts.find((entry) => entry.code === '4114' && entry.type === 'account') || salesRevenueAccount;
    const nonStockRevenueAccount = accounts.find((entry) => entry.code === '4115' && entry.type === 'account') || salesRevenueAccount;
    const salesDiscountAccount = this.getRequiredAccount(accounts, '4120', 'Sales Discounts');
    const vatPayableAccount = this.getRequiredAccount(accounts, '2140', 'VAT Payable');
    const customerAdvancesAccount = this.getRequiredAccount(accounts, '2170', 'Customer Advances');

    const exchangeRate = Number(entry.exchangeRate || 1) || 1;
    const invoiceTotal = roundAmount(entry.baseCurrencyAmount ?? (Number(entry.amount || 0) * exchangeRate));
    const taxAmount = roundAmount((Number(entry.taxAmount || 0)) * exchangeRate);
    const discountAmount = roundAmount((Number(entry.discountAmount || 0)) * exchangeRate);
    const netSales = roundAmount(Math.max(0, invoiceTotal - taxAmount));
    const grossSales = roundAmount(netSales + discountAmount);
    const normalizedPayments = this.normalizePaymentAmounts(entry.paymentMethods, exchangeRate);
    const lines: JournalEntryLine[] = [];

    const splitCoverageAmount = (amount: number) => {
      const split = resolveCoverageSplit({
        totalAmount: amount,
        secondaryCoveragePercent,
        secondaryCoverageAmount: roundAmount(Number(entry.secondaryCoverageAmount || 0) * exchangeRate),
        hasSecondaryCustomer: hasSecondaryCoverage,
      });
      if (!split.hasSecondaryCoverage || !secondaryReceivablesAccount) {
        return { primaryAmount: split.primaryAmount, secondaryAmount: 0 };
      }
      return {
        primaryAmount: split.primaryAmount,
        secondaryAmount: split.secondaryAmount,
      };
    };

    const pushReceivableDebitLines = (amount: number, baseDescription: string) => {
      const { primaryAmount, secondaryAmount } = splitCoverageAmount(amount);
      if (primaryAmount > 0) {
        this.pushLine(lines, {
          accountId: receivablesAccount.id,
          accountCode: receivablesAccount.code,
          accountName: receivablesAccount.nameAr,
          debit: primaryAmount,
          credit: 0,
          description: `${baseDescription} (العميل الرئيسي ${entry.customerName})`,
        });
      }
      if (secondaryReceivablesAccount && secondaryAmount > 0) {
        this.pushLine(lines, {
          accountId: secondaryReceivablesAccount.id,
          accountCode: secondaryReceivablesAccount.code,
          accountName: secondaryReceivablesAccount.nameAr,
          debit: secondaryAmount,
          credit: 0,
          description: `${baseDescription} (العميل الثانوي ${normalizedSecondaryCustomerName || normalizedSecondaryCustomerId})`,
        });
      }
    };

    const pushCustomerAdvanceCreditLines = (amount: number) => {
      const { primaryAmount, secondaryAmount } = splitCoverageAmount(amount);
      if (primaryAmount > 0) {
        this.pushLine(lines, {
          accountId: customerAdvancesAccount.id,
          accountCode: customerAdvancesAccount.code,
          accountName: customerAdvancesAccount.nameAr,
          debit: 0,
          credit: primaryAmount,
          description: `دفعة مقدمة من العميل الرئيسي ${entry.customerName}`,
        });
      }
      if (secondaryAmount > 0) {
        this.pushLine(lines, {
          accountId: customerAdvancesAccount.id,
          accountCode: customerAdvancesAccount.code,
          accountName: customerAdvancesAccount.nameAr,
          debit: 0,
          credit: secondaryAmount,
          description: `دفعة مقدمة من العميل الثانوي ${normalizedSecondaryCustomerName || normalizedSecondaryCustomerId}`,
        });
      }
    };

    if (normalizedPayments.length === 0) {
      pushReceivableDebitLines(invoiceTotal, 'ذمم العملاء من فاتورة المبيعات');
    } else {
      for (const payment of normalizedPayments) {
        if (payment.method === 'credit') {
          pushReceivableDebitLines(payment.amount, `ذمم العملاء من فاتورة ${entry.invoiceNumber}`);
          continue;
        }

        const targetAccount = payment.method === 'cash'
          ? cashAccount
          : payment.method === 'check'
            ? notesReceivableAccount
            : payment.method === 'bank_transfer'
              ? bankAccount
              : receivablesAccount;

        this.pushLine(lines, {
          accountId: targetAccount.id,
          accountCode: targetAccount.code,
          accountName: targetAccount.nameAr,
          debit: payment.amount,
          credit: 0,
          description: `فاتورة مبيعات ${entry.invoiceNumber} - ${entry.customerName}`,
        });
      }

      const totalAllocated = roundAmount(normalizedPayments.reduce((sum, payment) => sum + payment.amount, 0));
      if (totalAllocated < invoiceTotal) {
        pushReceivableDebitLines(roundAmount(invoiceTotal - totalAllocated), `ذمم العملاء من فاتورة ${entry.invoiceNumber}`);
      } else if (totalAllocated > invoiceTotal) {
        pushCustomerAdvanceCreditLines(roundAmount(totalAllocated - invoiceTotal));
      }
    }

    if (discountAmount > 0) {
      this.pushLine(lines, {
        accountId: salesDiscountAccount.id,
        accountCode: salesDiscountAccount.code,
        accountName: salesDiscountAccount.nameAr,
        debit: discountAmount,
        credit: 0,
        description: `خصم فاتورة ${entry.invoiceNumber}`,
      });
    }

    const revenueByCategory = await this.resolveRevenueCategoryAmounts(entry.invoiceId, grossSales);

    if (revenueByCategory.inventory > 0) {
      this.pushLine(lines, {
        accountId: salesRevenueAccount.id,
        accountCode: salesRevenueAccount.code,
        accountName: salesRevenueAccount.nameAr,
        debit: 0,
        credit: revenueByCategory.inventory,
        description: `إيراد فاتورة ${entry.invoiceNumber} - أصناف مخزنية`,
      });
    }

    if (revenueByCategory.service > 0) {
      this.pushLine(lines, {
        accountId: serviceRevenueAccount.id,
        accountCode: serviceRevenueAccount.code,
        accountName: serviceRevenueAccount.nameAr,
        debit: 0,
        credit: revenueByCategory.service,
        description: `إيراد فاتورة ${entry.invoiceNumber} - خدمات`,
      });
    }

    if (revenueByCategory.nonStock > 0) {
      this.pushLine(lines, {
        accountId: nonStockRevenueAccount.id,
        accountCode: nonStockRevenueAccount.code,
        accountName: nonStockRevenueAccount.nameAr,
        debit: 0,
        credit: revenueByCategory.nonStock,
        description: `إيراد فاتورة ${entry.invoiceNumber} - أصناف غير مخزنية`,
      });
    }

    if (taxAmount > 0) {
      this.pushLine(lines, {
        accountId: vatPayableAccount.id,
        accountCode: vatPayableAccount.code,
        accountName: vatPayableAccount.nameAr,
        debit: 0,
        credit: taxAmount,
        description: `ضريبة مبيعات فاتورة ${entry.invoiceNumber}`,
      });
    }

    const totalDebits = roundAmount(lines.reduce((sum, line) => sum + Number(line.debit || 0), 0));
    const totalCredits = roundAmount(lines.reduce((sum, line) => sum + Number(line.credit || 0), 0));

    if (Math.abs(totalDebits - totalCredits) > 0.01) {
      throw new Error(`Sales invoice journal is not balanced. Debits: ${totalDebits}, Credits: ${totalCredits}`);
    }

    await this.persistJournalEntry({
      id: resolveJournalId(entry),
      date: entry.date,
      description: `قيد فاتورة مبيعات ${entry.invoiceNumber} - ${entry.customerName}`,
      referenceType: 'sales_invoice',
      referenceId: entry.invoiceId,
      invoiceId: entry.invoiceId,
      shipmentId: entry.documentType === 'shipment' ? entry.invoiceId : undefined,
      lines,
      status: entry.status,
      createdBy: entry.createdBy,
      createdAt: new Date().toISOString(),
    });
  }

  /**
   * Post invoice to accounting
   */
  async postInvoice(invoiceId: string, invoiceNumber: string): Promise<void> {
    const draftId = buildShipmentDraftJournalId(invoiceId);
    const postedId = buildShipmentPostedJournalId(invoiceId);
    const draftEntry = await this.loadJournalEntryById(draftId);
    if (!draftEntry) return;

    await this.persistJournalEntry({
      ...draftEntry,
      id: postedId,
      status: 'posted',
      description: `قيد فاتورة مبيعات ${invoiceNumber}`,
      createdAt: new Date().toISOString(),
    });

    await pgDeleteJournalEntry(draftId);
  }

  /**
   * Reverse invoice in accounting
   */
  async reverseInvoice(invoiceId: string, invoiceNumber: string): Promise<void> {
    const existing = await this.loadJournalEntryById(buildSalesTaxJournalId(invoiceId));
    if (!existing) return;

    const reversedLines = existing.lines.map((line) => ({
      ...line,
      debit: roundAmount(Number(line.credit || 0)),
      credit: roundAmount(Number(line.debit || 0)),
    }));

    await this.persistJournalEntry({
      ...existing,
      id: buildSalesTaxJournalId(invoiceId),
      description: `عكس قيد فاتورة مبيعات ${invoiceNumber}`,
      lines: reversedLines,
      createdAt: new Date().toISOString(),
    });
  }

  /**
   * Record payment received
   */
  async recordPaymentReceived(
    invoiceId: string,
    invoiceNumber: string,
    amount: number,
    baseCurrencyAmount?: number,
    method?: string
  ): Promise<void> {
    return Promise.resolve();
  }
}
