/**
 * Default Sales Return Accounting Port Implementation
 * Wraps legacy accounting/journal functionality
 */

import {
  pgDeleteJournalEntry,
  pgGetChartOfAccounts,
  pgGetCustomers,
  pgGetSuppliers,
  pgSaveJournalEntry,
} from '@/lib/postgres/data-access';
import type { ChartOfAccount, JournalEntry, JournalEntryLine } from '@/lib/types';
import type { SalesReturnAccountingPort } from '../application/sales-return-accounting.port';

const SALES_RETURN_CREDIT_NOTE_PREFIX = 'JE-SR-CN';
const SALES_RETURN_REFUND_PREFIX = 'JE-SR-RF';

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

function buildCreditNoteJournalId(returnId: string): string {
  return `${SALES_RETURN_CREDIT_NOTE_PREFIX}-${String(returnId || '').trim()}`;
}

function buildRefundJournalId(returnId: string): string {
  return `${SALES_RETURN_REFUND_PREFIX}-${String(returnId || '').trim()}`;
}

export class DefaultSalesReturnAccountingPort
  implements SalesReturnAccountingPort
{
  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.type === 'account' && entry.code === code);
    if (!account) {
      throw new Error(`Missing chart-of-accounts mapping for ${label} (${code})`);
    }
    return account;
  }

  private async resolveCustomerReceivablesAccount(accounts: ChartOfAccount[], customerId: string): Promise<ChartOfAccount> {
    const fallback = this.getRequiredAccount(accounts, '1130', 'Accounts Receivable');
    const normalizedCustomerId = String(customerId || '').trim();
    if (!normalizedCustomerId) return fallback;

    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 customer = customers.find((entry) => String(entry.id || '').trim() === normalizedCustomerId);
    const supplier = suppliers.find((entry) => String(entry.id || '').trim() === normalizedCustomerId);

    const accountCode = String((customer as any)?.accountCode || (supplier as any)?.accountCode || '').trim();
    if (!accountCode) return fallback;

    return accounts.find((entry) => entry.type === 'account' && entry.code === accountCode) || fallback;
  }

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

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

  async recordReturnCreditNote(params: {
    returnId: string;
    invoiceId: string;
    customerId: string;
    amount: number;
    date: string;
    description: string;
  }): Promise<{
    journalEntryId: string;
    creditNoteId: string;
  }> {
    const accounts = await this.loadAccounts();
    const salesReturnAccount = this.getRequiredAccount(accounts, '4130', 'Sales Returns');
    const receivablesAccount = await this.resolveCustomerReceivablesAccount(accounts, params.customerId);

    const amount = roundAmount(Number(params.amount || 0));
    if (amount <= 0) {
      throw new Error('Sales return credit note amount must be greater than 0');
    }

    const journalEntryId = buildCreditNoteJournalId(params.returnId);
    const lines: JournalEntryLine[] = [];

    this.pushLine(lines, {
      accountId: salesReturnAccount.id,
      accountCode: salesReturnAccount.code,
      accountName: salesReturnAccount.nameAr,
      debit: amount,
      credit: 0,
      description: params.description,
    });

    this.pushLine(lines, {
      accountId: receivablesAccount.id,
      accountCode: receivablesAccount.code,
      accountName: receivablesAccount.nameAr,
      debit: 0,
      credit: amount,
      description: params.description,
    });

    await this.persistJournalEntry({
      id: journalEntryId,
      date: params.date,
      description: params.description,
      referenceType: 'manual',
      referenceId: params.returnId,
      lines,
      createdBy: 'system',
      createdAt: new Date().toISOString(),
    });

    return {
      journalEntryId,
      creditNoteId: `CN-${String(params.returnId || '').trim()}`,
    };
  }

  async recordRefund(params: {
    returnId: string;
    amount: number;
    date: string;
    refundMethod: string;
    description: string;
  }): Promise<{
    journalEntryId: string;
  }> {
    const amount = roundAmount(Number(params.amount || 0));
    if (amount <= 0) {
      throw new Error('Sales return refund amount must be greater than 0');
    }

    const accounts = await this.loadAccounts();
    const receivablesAccount = this.getRequiredAccount(accounts, '1130', 'Accounts Receivable');
    const cashAccount = this.getRequiredAccount(accounts, '1110', 'Cash');
    const bankAccount = this.getRequiredAccount(accounts, '1120', 'Bank');
    const notesReceivableAccount = this.getRequiredAccount(accounts, '1140', 'Notes Receivable');
    const customerAdvancesAccount = this.getRequiredAccount(accounts, '2170', 'Customer Advances');

    const normalizedMethod = String(params.refundMethod || '').trim();
    const offsetAccount = normalizedMethod === 'cash'
      ? cashAccount
      : normalizedMethod === 'bank_transfer'
        ? bankAccount
        : normalizedMethod === 'check'
          ? notesReceivableAccount
          : normalizedMethod === 'credit_to_account'
            ? customerAdvancesAccount
            : cashAccount;

    const journalEntryId = buildRefundJournalId(params.returnId);
    const lines: JournalEntryLine[] = [];

    this.pushLine(lines, {
      accountId: receivablesAccount.id,
      accountCode: receivablesAccount.code,
      accountName: receivablesAccount.nameAr,
      debit: amount,
      credit: 0,
      description: params.description,
    });

    this.pushLine(lines, {
      accountId: offsetAccount.id,
      accountCode: offsetAccount.code,
      accountName: offsetAccount.nameAr,
      debit: 0,
      credit: amount,
      description: params.description,
    });

    await this.persistJournalEntry({
      id: journalEntryId,
      date: params.date,
      description: `${params.description} (${normalizedMethod || 'cash'})`,
      referenceType: 'manual',
      referenceId: params.returnId,
      lines,
      createdBy: 'system',
      createdAt: new Date().toISOString(),
    });

    return {
      journalEntryId,
    };
  }

  async reverseReturnEntries(params: {
    returnId: string;
    reason: string;
  }): Promise<boolean> {
    const returnId = String(params.returnId || '').trim();
    if (!returnId) {
      throw new Error('Missing return ID for reverseReturnEntries');
    }

    const creditNoteJournalId = buildCreditNoteJournalId(returnId);
    const refundJournalId = buildRefundJournalId(returnId);

    await Promise.all([
      pgDeleteJournalEntry(creditNoteJournalId).catch(() => undefined),
      pgDeleteJournalEntry(refundJournalId).catch(() => undefined),
    ]);

    return true;
  }
}
