import { pgGetBankById, pgGetChartOfAccounts, pgGetJournalEntries, pgGetMaterials, pgGetPurchaseOrders, pgGetSettings, pgGetSuppliers, pgSaveChartOfAccounts, pgSaveJournalEntry, pgSaveSuppliers } from '@/lib/postgres/data-access';
import { AccountingEventType, AccountingPostingEngine, createDefaultEventRegistry, type JournalEntry as AccountingKernelJournalEntry } from '@/lib/accounting/core';
import type { ChartOfAccount, JournalEntry, JournalEntryLine, ProductionItem, PurchaseOrder, RealEstateProject } from '@/lib/types';
import type {
  PurchaseAccountingPort,
  PurchaseJournalPostingInput,
  ShipmentGrniInput,
} from '../application/purchase-accounting.port';
import { mapPurchaseInputToPostedEvent } from './purchase-accounting-event-mapper';

const PURCHASE_JOURNAL_ID_PREFIX = 'JE-PURCHASE-INVOICE';
const GRNI_JOURNAL_ID_PREFIX = 'JE-GRNI';
const GRNI_SETTLEMENT_ID_PREFIX = 'JE-GRNI-SETTLE';
/** Code for GRNI Payable liability account (auto-created if missing) */
const GRNI_ACCOUNT_CODE = '2115';

type PurchaseEventBridgeAttemptResult = {
  posted: boolean;
  reason?: string;
};

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

function buildPurchaseJournalId(reference: string): string {
  return `${PURCHASE_JOURNAL_ID_PREFIX}-${String(reference || '').trim()}`;
}

export class DefaultPurchaseAccountingPort implements PurchaseAccountingPort {
  private logPurchaseBridgeEvent(event: string, details: Record<string, unknown>) {
    console.info('[purchase-accounting-bridge]', JSON.stringify({ event, ...details }));
  }

  private isPurchaseEventEngineEnabled(): boolean {
    const value = String(process.env.ACCOUNTING_PURCHASE_EVENT_ENGINE || '').trim().toLowerCase();
    return value === '1' || value === 'true' || value === 'on';
  }

  private mapKernelJournalToLegacyEntry(
    journal: AccountingKernelJournalEntry,
    input: PurchaseJournalPostingInput,
    accounts: ChartOfAccount[],
  ): JournalEntry {
    const accountByCode = new Map(
      accounts
        .filter((entry) => entry.type === 'account')
        .map((entry) => [String(entry.code || '').trim(), entry] as const),
    );
    const fxRate = input.opts?.fxRate && input.opts.fxRate > 0 ? input.opts.fxRate : 1;
    const hasForeignCurrency = !!input.currencyCode && fxRate !== 1;

    const lines: JournalEntryLine[] = journal.lines.map((line) => {
      const accountCode = String(line.accountCode || '').trim();
      const account = accountByCode.get(accountCode);
      const debit = roundAmount(line.debit);
      const credit = roundAmount(line.credit);

      return {
        accountId: account?.id || accountCode,
        accountCode,
        accountName: account?.nameAr || accountCode,
        debit,
        credit,
        description: `Purchase Invoice ${input.orderNumber}`,
        ...(hasForeignCurrency && {
          currencyCode: input.currencyCode,
          originalDebit: debit > 0 ? roundAmount(debit * fxRate) : 0,
          originalCredit: credit > 0 ? roundAmount(credit * fxRate) : 0,
        }),
      };
    });

    return {
      id: buildPurchaseJournalId(String(input.opts?.invoiceId || input.orderNumber || 'purchase')),
      date: journal.createdAt,
      description: `Purchase Invoice ${input.orderNumber} from ${input.supplierName}`,
      referenceType: 'purchase_order',
      referenceId: input.orderNumber,
      invoiceId: input.opts?.invoiceId,
      shipmentId: input.opts?.shipmentId,
      shipmentType: input.opts?.shipmentType,
      lines,
      createdBy: input.createdBy,
      createdAt: journal.createdAt,
      ...(hasForeignCurrency && { currencyCode: input.currencyCode, fxRate }),
    };
  }

  private async tryPostPurchaseViaEventEngine(
    input: PurchaseJournalPostingInput,
    accounts: ChartOfAccount[],
    supplierAccountCode: string,
  ): Promise<PurchaseEventBridgeAttemptResult> {
    try {
      const { payload, context } = mapPurchaseInputToPostedEvent(input, {
        supplierAccountCode,
        timestamp: new Date().toISOString(),
      });
      const engine = new AccountingPostingEngine(createDefaultEventRegistry());
      const result = await engine.postAccountingEvent(
        AccountingEventType.PURCHASE_POSTED,
        payload,
        context,
      );

      if (!result.success || !result.journal) {
        return {
          posted: false,
          reason: result.success ? 'missing-journal' : (result.error || 'engine-rejected'),
        };
      }

      const entry = this.mapKernelJournalToLegacyEntry(result.journal, input, accounts);
      await this.persistJournalEntry(entry);
      return { posted: true };
    } catch (error) {
      return {
        posted: false,
        reason: error instanceof Error ? error.message : 'unexpected-event-engine-error',
      };
    }
  }

  private allocateSupplierAccountCode(accounts: ChartOfAccount[], parentCode: string): string {
    const pattern = new RegExp(`^${parentCode.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-([0-9]+)$`);
    let maxSequence = 0;
    accounts.forEach((entry) => {
      const match = String(entry.code || '').trim().match(pattern);
      if (!match) return;
      const sequence = Number.parseInt(match[1], 10);
      if (Number.isFinite(sequence)) {
        maxSequence = Math.max(maxSequence, sequence);
      }
    });
    return `${parentCode}-${String(maxSequence + 1).padStart(3, '0')}`;
  }

  private async ensureSupplierPostingAccount(accounts: ChartOfAccount[], suppliers: Array<Record<string, unknown>>, supplierId: string, supplierName: string): Promise<ChartOfAccount> {
    const supplierIndex = suppliers.findIndex((entry) => String((entry as any)?.id || '').trim() === String(supplierId || '').trim());
    if (supplierIndex < 0) {
      throw new Error(`Supplier not found for purchase posting: ${supplierId}`);
    }

    const supplier = suppliers[supplierIndex] as Record<string, unknown>;
    const configuredCode = String((supplier as any)?.accountCode || '').trim();
    const configuredAccount = configuredCode
      ? accounts.find((entry) => String(entry.code || '').trim() === configuredCode && entry.type === 'account')
      : undefined;

    if (configuredAccount && configuredCode !== '2110' && configuredCode !== '2111') {
      return configuredAccount;
    }

    let accountsChanged = false;
    let supplierParent = accounts.find((entry) => entry.code === '2111' && entry.type === 'account');
    if (!supplierParent) {
      supplierParent = {
        id: '2111',
        code: '2111',
        nameAr: 'الموردون',
        nameEn: 'Suppliers',
        type: 'account',
        parentId: '2100',
        nature: 'credit',
      };
      accounts.push(supplierParent);
      accountsChanged = true;
    }

    const normalizedSupplierName = String((supplier as any)?.name || supplierName || '').trim() || `Supplier ${supplierId}`;
    let supplierAccount = accounts.find((entry) =>
      entry.type === 'account'
      && String(entry.parentId || '').trim() === String(supplierParent?.id || '').trim()
      && String(entry.nameAr || '').trim() === normalizedSupplierName,
    );

    if (!supplierAccount) {
      const accountCode = this.allocateSupplierAccountCode(accounts, String(supplierParent.code || '2111'));
      supplierAccount = {
        id: accountCode,
        code: accountCode,
        nameAr: normalizedSupplierName,
        nameEn: normalizedSupplierName,
        type: 'account',
        parentId: supplierParent.id,
        nature: 'credit',
      };
      accounts.push(supplierAccount);
      accountsChanged = true;
    }

    const nextSupplier = {
      ...supplier,
      accountId: supplierAccount.id,
      accountCode: supplierAccount.code,
    };
    suppliers[supplierIndex] = nextSupplier;

    if (accountsChanged) {
      
      await pgSaveChartOfAccounts(accounts as unknown as Array<Record<string, unknown>>);
      
    }

    
    await pgSaveSuppliers(suppliers as Array<Record<string, unknown>>);
    

    return supplierAccount;
  }

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

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

  private async loadPurchaseOrders(): Promise<PurchaseOrder[]> {
    
    const result = await pgGetPurchaseOrders({ page: 1, pageSize: 5000 });
    return (result.items || []) as PurchaseOrder[];
  }

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

  private async loadProjects(): Promise<RealEstateProject[]> {
    const settings = await pgGetSettings();
    return Array.isArray((settings as any)?.realEstateProjects)
      ? (((settings as any).realEstateProjects as RealEstateProject[]).filter((entry) => entry && typeof entry === 'object'))
      : [];
  }

  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, allowZero = false) {
    const debit = roundAmount(nextLine.debit);
    const credit = roundAmount(nextLine.credit);
    if (!allowZero && 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 async persistJournalEntry(entry: JournalEntry): Promise<void> {
    
    await pgSaveJournalEntry([entry as unknown as Record<string, unknown>]);
    return;
  }

  private async resolveBankAccount(method: any, accounts: ChartOfAccount[], defaultAccount: ChartOfAccount): Promise<ChartOfAccount> {
    const bankId = String(method?.bankId || '').trim();
    if (!bankId) return defaultAccount;

    const bank = await pgGetBankById(bankId);    const bankAccountCode = String((bank as any)?.accountCode || '').trim();
    if (!bankAccountCode) return defaultAccount;

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

  private async buildDebitLines(accounts: ChartOfAccount[], input: PurchaseJournalPostingInput): Promise<JournalEntryLine[]> {
    const purchasesAccount = this.getRequiredAccount(accounts, '5110', 'Purchases');
    const servicePurchasesAccount = accounts.find((entry) => entry.code === '5130' && entry.type === 'account') || purchasesAccount;
    const nonStockPurchasesAccount = accounts.find((entry) => entry.code === '5120' && entry.type === 'account') || purchasesAccount;
    const orders = await this.loadPurchaseOrders();
    const order = orders.find((entry) =>
      String(entry.id || '').trim() === String(input.opts?.invoiceId || '').trim()
      || String(entry.orderNumber || '').trim() === String(input.orderNumber || '').trim()
    );

    if (!order || !Array.isArray(order.items) || order.items.length === 0) {
      return [{
        accountId: purchasesAccount.id,
        accountCode: purchasesAccount.code,
        accountName: purchasesAccount.nameAr,
        debit: roundAmount(input.totalAmountBase),
        credit: 0,
        description: `Purchase Invoice ${input.orderNumber}`,
      }];
    }

    const materials = await this.loadMaterials();
    const projects = await this.loadProjects();
    const materialById = new Map(materials.map((material) => [String(material.id || '').trim(), material]));
    const projectById = new Map(projects.map((project) => [String(project.id || '').trim(), project]));
    const lines: JournalEntryLine[] = [];
    const subTotal = Math.max(0, order.items.reduce((sum, item) => sum + Math.max(0, Number(item.total || 0)), 0));
    let allocated = 0;

    order.items.forEach((item, index) => {
      const material = materialById.get(String(item.materialId || '').trim());
      const itemCategory = String(material?.itemCategory || '').trim();
      const isService = itemCategory === 'service';
      const isNonStock = itemCategory === 'non_stock';
      const projectId = String(item.realEstateProjectId || order.realEstateProjectId || material?.realEstateProjectId || '').trim();
      if (subTotal <= 0) return;

      const lineWeight = Number(item.total || 0) > 0 ? Number(item.total || 0) / subTotal : 0;
      const lineAmountBase = roundAmount(Number(input.totalAmountBase || 0) * lineWeight);
      if (lineAmountBase <= 0) return;

      if (isService || isNonStock) {
        const targetAccount = isService ? servicePurchasesAccount : nonStockPurchasesAccount;
        allocated = roundAmount(allocated + lineAmountBase);
        this.pushLine(lines, {
          accountId: targetAccount.id,
          accountCode: targetAccount.code,
          accountName: targetAccount.nameAr,
          debit: lineAmountBase,
          credit: 0,
          description: `${isService ? 'شراء خدمة' : 'شراء صنف غير مخزني'} - ${item.name || input.orderNumber}`,
        });
        return;
      }

      if (!projectId) {
        allocated = roundAmount(allocated + lineAmountBase);
        this.pushLine(lines, {
          accountId: purchasesAccount.id,
          accountCode: purchasesAccount.code,
          accountName: purchasesAccount.nameAr,
          debit: lineAmountBase,
          credit: 0,
          description: `Purchase Invoice ${input.orderNumber} - line ${index + 1}`,
        });
        return;
      }

      const project = projectById.get(projectId);
      const lineScope = String(
        item.realEstateScope
        || (order.realEstateLinkMode === 'order' ? (order.realEstateScope || 'project') : '')
        || ''
      ).trim();
      const floorLabel = String(
        item.realEstateFloorLabel
        || (order.realEstateLinkMode === 'order' ? order.realEstateFloorLabel : '')
        || ''
      ).trim();
      const partLabel = String(
        item.realEstatePartLabel
        || (order.realEstateLinkMode === 'order' ? order.realEstatePartLabel : '')
        || ''
      ).trim();
      const unit = (project?.units || []).find((entry) => String(entry.id || '').trim() === String(material?.realEstateUnitId || '').trim());
      const resolvedCostType = lineScope === 'project'
        ? 'shared-project'
        : lineScope === 'floor' || lineScope === 'part'
          ? 'direct-unit'
          : (material?.realEstateCostType || 'general');
      const fallbackAccountCode = String(
        resolvedCostType === 'shared-project'
          ? project?.accounting?.sharedCostAccountCode
          : project?.accounting?.directCostAccountCode
      || '').trim();
      const targetAccountCode = String(project?.accounting?.projectAccountCode || fallbackAccountCode || purchasesAccount.code).trim();
      const targetAccount = accounts.find((entry) => entry.code === targetAccountCode && entry.type === 'account') || purchasesAccount;

      allocated = roundAmount(allocated + lineAmountBase);
      const costTypeLabel = resolvedCostType === 'direct-unit'
        ? 'تكلفة مباشرة'
        : resolvedCostType === 'shared-project'
          ? 'تكلفة مشتركة'
          : 'تكلفة مشروع';
      const scopeDetails = [floorLabel, partLabel].filter(Boolean).join(' - ');
      const lineDescription = project
        ? `${costTypeLabel} للمشروع ${project.name}${scopeDetails ? ` - ${scopeDetails}` : ''}${unit ? ` - ${unit.name || unit.unitCode}` : ''}`
        : `Purchase Invoice ${input.orderNumber} - line ${index + 1}`;

      this.pushLine(lines, {
        accountId: targetAccount.id,
        accountCode: targetAccount.code,
        accountName: targetAccount.nameAr,
        debit: lineAmountBase,
        credit: 0,
        description: lineDescription,
      });
    });

    const remainder = roundAmount(Number(input.totalAmountBase || 0) - allocated);
    if (remainder > 0 || lines.length === 0) {
      this.pushLine(lines, {
        accountId: purchasesAccount.id,
        accountCode: purchasesAccount.code,
        accountName: purchasesAccount.nameAr,
        debit: remainder > 0 ? remainder : roundAmount(input.totalAmountBase),
        credit: 0,
        description: `Purchase Invoice ${input.orderNumber}`,
      }, true);
    }

    return lines;
  }

  async postPurchaseOrderJournalEntry(input: PurchaseJournalPostingInput): Promise<void> {
    try {
      const accounts = await this.loadAccounts();
      const suppliers = await this.loadSuppliers();
      const supplierAccount = await this.ensureSupplierPostingAccount(accounts, suppliers, input.supplierId, input.supplierName);
      const invoiceRef = String(input.opts?.invoiceId || input.orderNumber || 'purchase').trim();
      const eventEngineEnabled = this.isPurchaseEventEngineEnabled();

      this.logPurchaseBridgeEvent(eventEngineEnabled ? 'bridge-enabled' : 'bridge-disabled', {
        invoiceRef,
        supplierId: input.supplierId,
        orderNumber: input.orderNumber,
      });

      if (eventEngineEnabled) {
        const bridgeAttempt = await this.tryPostPurchaseViaEventEngine(input, accounts, String(supplierAccount.code || '').trim());
        if (bridgeAttempt.posted) {
          this.logPurchaseBridgeEvent('event-engine-posted', {
            invoiceRef,
            supplierId: input.supplierId,
            orderNumber: input.orderNumber,
          });
          return;
        }

        this.logPurchaseBridgeEvent('fallback-to-legacy', {
          invoiceRef,
          supplierId: input.supplierId,
          orderNumber: input.orderNumber,
          reason: bridgeAttempt.reason || 'unknown',
        });
      }

      const cashAccount = this.getRequiredAccount(accounts, '1110', 'Cash');
      const defaultBankAccount = this.getRequiredAccount(accounts, '1120', 'Bank');
      const notesReceivableAccount = this.getRequiredAccount(accounts, '1140', 'Notes Receivable');
      const purchaseInputTaxAccount = accounts.find((entry) => entry.code === '1160' && entry.type === 'account') || this.getRequiredAccount(accounts, '5110', 'Purchases');

      const fxRate = input.opts?.fxRate && input.opts.fxRate > 0 ? input.opts.fxRate : 1;
      const hasForeignCurrency = !!(input.currencyCode) && fxRate !== 1;

      /** Attach original-currency amounts to a line if there is a foreign currency */
      const withFx = (line: JournalEntryLine): JournalEntryLine => {
        if (!hasForeignCurrency) return line;
        return {
          ...line,
          currencyCode: input.currencyCode,
          originalDebit: line.debit > 0 ? roundAmount(line.debit * fxRate) : 0,
          originalCredit: line.credit > 0 ? roundAmount(line.credit * fxRate) : 0,
        };
      };

      const safeTaxAmountBase = roundAmount(Math.max(0, Number(input.taxAmountBase || 0)));
      const grossAmountBase = roundAmount(Number(input.totalAmountBase || 0) + safeTaxAmountBase);
      const rawLines = await this.buildDebitLines(accounts, input);
      const lines = rawLines.map(withFx);

      if (safeTaxAmountBase > 0) {
        this.pushLine(lines, withFx({
          accountId: purchaseInputTaxAccount.id,
          accountCode: purchaseInputTaxAccount.code,
          accountName: purchaseInputTaxAccount.nameAr,
          debit: safeTaxAmountBase,
          credit: 0,
          description: `Input tax for ${input.orderNumber}`,
        }));
      }

      this.pushLine(lines, withFx({
        accountId: supplierAccount.id,
        accountCode: supplierAccount.code,
        accountName: supplierAccount.nameAr,
        debit: 0,
        credit: grossAmountBase,
        description: `From ${input.supplierName}`,
      }), true);

      if (Number(input.amountPaidBase || 0) > 0) {
        const methodsList: any[] = input.opts?.paymentMethods || [];

        if (methodsList.length > 0) {
          for (const method of methodsList) {
            const methodAmount = Number(method.amount || 0);
            if (methodAmount <= 0) continue;
            const methodAmountBase = roundAmount(methodAmount / fxRate);

            let settlementAccount = cashAccount;
            if (method.method === 'check' && method.checkSource === 'incoming') {
              settlementAccount = notesReceivableAccount;
            } else if (method.method === 'check' || method.method === 'bank_transfer') {
              settlementAccount = await this.resolveBankAccount(method, accounts, defaultBankAccount);
            }

            this.pushLine(lines, withFx({
              accountId: supplierAccount.id,
              accountCode: supplierAccount.code,
              accountName: supplierAccount.nameAr,
              debit: methodAmountBase,
              credit: 0,
              description: `Payment for ${input.orderNumber}`,
            }));

            this.pushLine(lines, withFx({
              accountId: settlementAccount.id,
              accountCode: settlementAccount.code,
              accountName: settlementAccount.nameAr,
              debit: 0,
              credit: methodAmountBase,
              description: `Payment for ${input.orderNumber}`,
            }));
          }
        } else {
          this.pushLine(lines, withFx({
            accountId: supplierAccount.id,
            accountCode: supplierAccount.code,
            accountName: supplierAccount.nameAr,
            debit: roundAmount(Number(input.amountPaidBase || 0)),
            credit: 0,
            description: `Payment for ${input.orderNumber}`,
          }));
          this.pushLine(lines, withFx({
            accountId: cashAccount.id,
            accountCode: cashAccount.code,
            accountName: cashAccount.nameAr,
            debit: 0,
            credit: roundAmount(Number(input.amountPaidBase || 0)),
            description: `Payment for ${input.orderNumber}`,
          }));
        }
      }

      const entry: JournalEntry = {
        id: buildPurchaseJournalId(String(input.opts?.invoiceId || input.orderNumber || 'purchase')),
        date: new Date().toISOString(),
        description: `Purchase Invoice ${input.orderNumber} from ${input.supplierName}`,
        referenceType: 'purchase_order',
        referenceId: input.orderNumber,
        invoiceId: input.opts?.invoiceId,
        shipmentId: input.opts?.shipmentId,
        shipmentType: input.opts?.shipmentType,
        lines,
        createdBy: input.createdBy,
        createdAt: new Date().toISOString(),
        ...(hasForeignCurrency && { currencyCode: input.currencyCode, fxRate }),
      };

      await this.persistJournalEntry(entry);
      this.logPurchaseBridgeEvent('legacy-posted', {
        invoiceRef,
        supplierId: input.supplierId,
        orderNumber: input.orderNumber,
      });
    } catch (error) {
      console.error('Failed to create journal entry:', error);
    }
  }

  private async ensureGrniAccount(accounts: ChartOfAccount[]): Promise<ChartOfAccount> {
    const existing = accounts.find((a) => a.code === GRNI_ACCOUNT_CODE && a.type === 'account');
    if (existing) return existing;

    const grniAccount: ChartOfAccount = {
      id: GRNI_ACCOUNT_CODE,
      code: GRNI_ACCOUNT_CODE,
      nameAr: 'بضاعة مستلمة غير مفوترة (GRNI)',
      nameEn: 'Goods Received Not Invoiced (GRNI)',
      type: 'account',
      parentId: '2100',
      nature: 'credit',
    };
    accounts.push(grniAccount);
    
    await pgSaveChartOfAccounts(accounts as unknown as Array<Record<string, unknown>>);
    
    return grniAccount;
  }

  async postShipmentGrniEntry(input: ShipmentGrniInput): Promise<void> {
    try {
      const amount = roundAmount(Math.max(0, Number(input.totalAmountBase || 0)));
      if (amount <= 0) return;

      const accounts = await this.loadAccounts();
      const purchasesAccount = this.getRequiredAccount(accounts, '5110', 'Purchases');
      const grniAccount = await this.ensureGrniAccount(accounts);

      const entry: JournalEntry = {
        id: `${GRNI_JOURNAL_ID_PREFIX}-${String(input.shipmentOrderNumber || '').trim()}`,
        date: new Date().toISOString(),
        description: `GRNI - ارسالية ${input.shipmentOrderNumber} من ${input.supplierName}`,
        status: 'draft',
        referenceType: 'purchase_order',
        referenceId: input.shipmentOrderNumber,
        shipmentId: input.shipmentId,
        lines: [
          {
            accountId: purchasesAccount.id,
            accountCode: purchasesAccount.code,
            accountName: purchasesAccount.nameAr,
            debit: amount,
            credit: 0,
            description: `ارسالية ${input.shipmentOrderNumber} - مشتريات مؤقتة`,
          },
          {
            accountId: grniAccount.id,
            accountCode: grniAccount.code,
            accountName: grniAccount.nameAr,
            debit: 0,
            credit: amount,
            description: `GRNI - ${input.supplierName}`,
          },
        ],
        createdBy: input.createdBy,
        createdAt: new Date().toISOString(),
      };

      await this.persistJournalEntry(entry);
    } catch (error) {
      console.error('Failed to create GRNI entry:', error);
    }
  }

  async settleGrniEntries(shipmentIds: string[], invoiceOrderNumber: string, createdBy: string): Promise<void> {
    try {
      if (!shipmentIds.length) return;

      // Load existing journal entries to find matching GRNI drafts
      const allEntries: JournalEntry[] = (((await pgGetJournalEntries({ page: 1, pageSize: 10000 })).items || []) as JournalEntry[]);
      const grniEntries = allEntries.filter(
        (e) =>
          e.id.startsWith(GRNI_JOURNAL_ID_PREFIX + '-') &&
          !e.id.startsWith(GRNI_SETTLEMENT_ID_PREFIX) &&
          e.shipmentId &&
          shipmentIds.includes(e.shipmentId),
      );

      if (!grniEntries.length) return;

      const accounts = await this.loadAccounts();
      const purchasesAccount = this.getRequiredAccount(accounts, '5110', 'Purchases');
      const grniAccount = await this.ensureGrniAccount(accounts);

      // Build a single settlement entry reversing all GRNI lines
      const settlementLines: JournalEntryLine[] = [];
      for (const grni of grniEntries) {
        const grniCreditLine = grni.lines.find((l) => l.accountCode === GRNI_ACCOUNT_CODE);
        const amount = roundAmount(Number(grniCreditLine?.credit || 0));
        if (amount <= 0) continue;

        settlementLines.push({
          accountId: grniAccount.id,
          accountCode: grniAccount.code,
          accountName: grniAccount.nameAr,
          debit: amount,
          credit: 0,
          description: `تسوية GRNI - ${grni.referenceId}`,
        });
        settlementLines.push({
          accountId: purchasesAccount.id,
          accountCode: purchasesAccount.code,
          accountName: purchasesAccount.nameAr,
          debit: 0,
          credit: amount,
          description: `تسوية GRNI - ${grni.referenceId}`,
        });
      }

      if (!settlementLines.length) return;

      const settlementEntry: JournalEntry = {
        id: `${GRNI_SETTLEMENT_ID_PREFIX}-${String(invoiceOrderNumber || '').trim()}`,
        date: new Date().toISOString(),
        description: `تسوية GRNI للفاتورة ${invoiceOrderNumber}`,
        status: 'posted',
        referenceType: 'purchase_order',
        referenceId: invoiceOrderNumber,
        lines: settlementLines,
        createdBy,
        createdAt: new Date().toISOString(),
      };

      await this.persistJournalEntry(settlementEntry);
    } catch (error) {
      console.error('Failed to settle GRNI entries:', error);
    }
  }
}

export function createDefaultPurchaseAccountingPort(): PurchaseAccountingPort {
  return new DefaultPurchaseAccountingPort();
}
