import {
  type AccountingEventSchema,
  AccountingEventType,
  type ValidationResult,
} from './accounting-event-registry';
import type { PostingContext } from './context-types';
import { computeJournalHash } from './hash-chain';
import type { JournalEntry, JournalLine } from './journal-types';

export type PurchasePaymentMethod = 'none' | 'cash' | 'bank_transfer' | 'incoming_check';

export interface PurchasePostedPayload {
  netAmountBase: number;
  taxAmountBase: number;
  amountPaidBase: number;
  supplierAccountCode: string;
  purchasesAccountCode?: string;
  inputTaxAccountCode?: string;
  cashAccountCode?: string;
  bankAccountCode?: string;
  notesReceivableAccountCode?: string;
  transactionCurrency?: string;
  exchangeRateSnapshot?: number;
  currencyPrecision?: number;
  paymentMethod?: PurchasePaymentMethod;
  branchId?: string;
}

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

function sumDebit(lines: JournalLine[]): number {
  return round2(lines.reduce((acc, line) => acc + Number(line.debit || 0), 0));
}

function sumCredit(lines: JournalLine[]): number {
  return round2(lines.reduce((acc, line) => acc + Number(line.credit || 0), 0));
}

function validate(payload: PurchasePostedPayload): ValidationResult {
  const errors: string[] = [];

  const net = round2(payload.netAmountBase);
  const tax = round2(payload.taxAmountBase);
  const paid = round2(payload.amountPaidBase);
  const gross = round2(net + tax);

  if (!payload.supplierAccountCode?.trim()) errors.push('supplierAccountCode is required');
  if (net < 0) errors.push('netAmountBase must be >= 0');
  if (tax < 0) errors.push('taxAmountBase must be >= 0');
  if (paid < 0) errors.push('amountPaidBase must be >= 0');
  if (gross <= 0) errors.push('gross amount must be > 0');
  if (paid > gross) errors.push('amountPaidBase cannot exceed gross amount');

  return { valid: errors.length === 0, errors };
}

function resolveSettlementAccount(payload: PurchasePostedPayload): string {
  if (payload.paymentMethod === 'incoming_check') {
    return payload.notesReceivableAccountCode || '1140';
  }
  if (payload.paymentMethod === 'bank_transfer') {
    return payload.bankAccountCode || '1120';
  }
  return payload.cashAccountCode || '1110';
}

function buildLines(payload: PurchasePostedPayload): JournalLine[] {
  const net = round2(payload.netAmountBase);
  const tax = round2(payload.taxAmountBase);
  const paid = round2(payload.amountPaidBase);
  const gross = round2(net + tax);
  const branchId = payload.branchId || 'main';

  const lines: JournalLine[] = [
    {
      accountCode: payload.purchasesAccountCode || '5110',
      debit: net,
      credit: 0,
      branchId,
    },
    {
      accountCode: payload.supplierAccountCode,
      debit: 0,
      credit: gross,
      branchId,
    },
  ];

  if (tax > 0) {
    lines.push({
      accountCode: payload.inputTaxAccountCode || '1160',
      debit: tax,
      credit: 0,
      branchId,
    });
  }

  if (paid > 0) {
    lines.push({
      accountCode: payload.supplierAccountCode,
      debit: paid,
      credit: 0,
      branchId,
    });
    lines.push({
      accountCode: resolveSettlementAccount(payload),
      debit: 0,
      credit: paid,
      branchId,
    });
  }

  return lines;
}

function buildJournal(payload: PurchasePostedPayload, context: PostingContext): JournalEntry {
  const lines = buildLines(payload);
  const entry: JournalEntry = {
    id: `JE-PURCHASE-${context.documentId}-${context.postingSequence}`,
    documentId: context.documentId,
    postingSequence: context.postingSequence,
    eventType: AccountingEventType.PURCHASE_POSTED,
    previousJournalHash: null,
    currentJournalHash: '',
    baseCurrency: 'USD',
    transactionCurrency: payload.transactionCurrency || 'USD',
    exchangeRateSnapshot: payload.exchangeRateSnapshot || 1,
    currencyPrecision: payload.currencyPrecision || 2,
    lines,
    createdAt: context.timestamp,
    postingRuleVersion: context.postingRuleVersion,
  };

  entry.currentJournalHash = computeJournalHash(entry);
  return entry;
}

export const purchasePostedEventSchema: AccountingEventSchema<PurchasePostedPayload> = {
  type: AccountingEventType.PURCHASE_POSTED,
  version: 'v1',
  validate,
  async handler(payload, context) {
    const journal = buildJournal(payload, context);
    if (sumDebit(journal.lines) !== sumCredit(journal.lines)) {
      return {
        success: false,
        error: 'journal is not balanced',
      };
    }

    return {
      success: true,
      journalId: journal.id,
      journal,
    };
  },
};
