// Ledger Posting Module
// ONLY module allowed to mutate ledger_entries and bank_accounts
// Enforces atomic SQL function usage for correctness

import { Decimal } from '@prisma/client/runtime/library';
import prisma from '@/shared/database/prisma';
import { DbisError, ErrorCode } from '@/shared/types';
import { logger } from '@/infrastructure/monitoring/logger';

export interface PostEntryParams {
  ledgerId: string;
  debitAccountId: string;
  creditAccountId: string;
  amount: string;
  currencyCode: string;
  assetType: string;
  transactionType: string;
  referenceId: string;
  fxRate?: string;
  metadata?: Record<string, unknown>;
}

export interface PostEntryResult {
  entryId: string;
  blockHash: string;
  debitBalance: string;
  creditBalance: string;
}

/**
 * Ledger Posting Module
 * 
 * CRITICAL: This is the ONLY module allowed to mutate:
 * - ledger_entries
 * - bank_accounts (balance updates)
 * 
 * All mutations must go through the atomic SQL function post_ledger_entry()
 * to ensure correctness at the database level.
 */
export class LedgerPostingModule {
  /**
   * Post ledger entry - ONLY way to mutate ledger
   * Enforces atomic SQL function usage
   */
  async postEntry(params: PostEntryParams): Promise<PostEntryResult> {
    logger.info('LedgerPostingModule: Posting entry', {
      ledgerId: params.ledgerId,
      referenceId: params.referenceId,
      amount: params.amount,
    });

    // Validate accounts exist (before calling SQL function)
    const debitAccount = await prisma.bank_accounts.findUnique({
      where: { id: params.debitAccountId },
    });
    const creditAccount = await prisma.bank_accounts.findUnique({
      where: { id: params.creditAccountId },
    });

    if (!debitAccount || !creditAccount) {
      throw new DbisError(ErrorCode.NOT_FOUND, 'Account not found');
    }

    // Validate currency matches
    if (
      debitAccount.currencyCode !== params.currencyCode ||
      creditAccount.currencyCode !== params.currencyCode
    ) {
      throw new DbisError(
        ErrorCode.VALIDATION_ERROR,
        'Currency mismatch between accounts'
      );
    }

    // Call atomic SQL function
    const result = await this.postEntryViaSqlFunction(params);

    logger.info('LedgerPostingModule: Entry posted', {
      entryId: result.entryId,
      blockHash: result.blockHash,
    });

    return result;
  }

  /**
   * Post entry via atomic SQL function
   * This is the ONLY place that should call post_ledger_entry()
   */
  private async postEntryViaSqlFunction(
    params: PostEntryParams
  ): Promise<PostEntryResult> {
    try {
      const result = await prisma.$queryRaw<Array<{
        entry_id: string;
        block_hash: string;
        debit_balance: Decimal;
        credit_balance: Decimal;
      }>>`
        SELECT * FROM post_ledger_entry(
          ${params.ledgerId}::TEXT,
          ${params.debitAccountId}::TEXT,
          ${params.creditAccountId}::TEXT,
          ${params.amount}::NUMERIC,
          ${params.currencyCode}::TEXT,
          ${params.assetType}::TEXT,
          ${params.transactionType}::TEXT,
          ${params.referenceId}::TEXT,
          ${params.fxRate || null}::NUMERIC,
          ${params.metadata ? JSON.stringify(params.metadata) : null}::JSONB
        )
      `;

      if (!result || result.length === 0) {
        throw new DbisError(
          ErrorCode.INTERNAL_ERROR,
          'SQL function returned no result'
        );
      }

      const row = result[0];
      return {
        entryId: row.entry_id,
        blockHash: row.block_hash,
        debitBalance: row.debit_balance.toString(),
        creditBalance: row.credit_balance.toString(),
      };
    } catch (error: any) {
      // Handle PostgreSQL errors
      if (error?.code === '23505') {
        // Unique constraint violation (duplicate reference_id)
        throw new DbisError(
          ErrorCode.VALIDATION_ERROR,
          `Duplicate reference_id: ${params.referenceId}`
        );
      }

      if (error?.message?.includes('Insufficient balance')) {
        throw new DbisError(ErrorCode.VALIDATION_ERROR, error.message);
      }

      if (error?.message?.includes('Duplicate reference_id')) {
        throw new DbisError(ErrorCode.VALIDATION_ERROR, error.message);
      }

      logger.error('LedgerPostingModule: SQL function error', {
        error: error?.message,
        code: error?.code,
        params: {
          ledgerId: params.ledgerId,
          referenceId: params.referenceId,
        },
      });

      throw error;
    }
  }

  /**
   * BANNED: Direct balance updates
   * @deprecated - Use postEntry() instead
   */
  private async updateBalanceDirectly() {
    throw new Error(
      'Direct balance updates are forbidden. Use postEntry() instead.'
    );
  }
}

// Export singleton - enforce single access point
export const ledgerPostingModule = new LedgerPostingModule();
