import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
import { TestDatabase } from '../../../__tests__/helpers/db-setup.js';
import { insertFixture } from '../../../__tests__/helpers/fixture-loader.js';
import { expenseScenarioA } from '../../../__tests__/fixtures/expenses/expense-scenario-a.js';
import { makeUUID } from '../../../demo-fixtures/helpers/deterministic-uuid.js';
import { qualifyTable } from '../../../__tests__/helpers/test-db-config.js';
import { buildAdminContextFromDb } from '../../../__tests__/helpers/admin-context-builder.js';
import { env } from '../../../environment.js';
import { ledgerGenerationByCharge } from '../helpers/ledger-by-charge-type.helper.js';
import { assertSimpleLocalExpenseScenario, assertAuditTrail } from './helpers/ledger-assertions.js';
import { createLedgerTestContext } from '../../../test-utils/ledger-injector.js';
import { Currency } from '../../../shared/enums.js';
/**
 * Ledger Integration Test for Expense Scenario A
 *
 * Tests ledger generation for a simple ILS receipt expense scenario.
 * This validates the complete flow: fixture insertion → ledger generation → record validation.
 *
 * Scenario:
 * - Admin business purchases from Local Supplier Ltd
 * - Transaction: -500 ILS (expense)
 * - Document: Receipt for 500 ILS
 * - Expected ledger: 2 balanced records (debit expense, credit bank)
 */
describe('Ledger Generation - Expense Scenario A', () => {
    let db;
    beforeAll(async () => {
        db = new TestDatabase();
        await db.connect();
        await db.ensureLatestSchema();
        await db.seedAdmin();
    });
    afterAll(async () => {
        // Do NOT close db here — the shared pool is managed by vitest-global-setup teardown.
        // Closing it here destroys the pool for all other concurrently-running integration test suites.
    });
    // Ensure ledger and related rows do not leak between tests
    afterEach(async () => {
        const client = await db.getPool().connect();
        try {
            const chargeId = makeUUID('charge', 'charge-office-supplies');
            const adminId = makeUUID('business', 'admin-business-scenario-a');
            // Set context to allow deletion of protected rows
            await client.query("SELECT set_config('app.current_business_id', $1, false)", [adminId]);
            await client.query(`DELETE FROM ${qualifyTable('ledger_records')} WHERE charge_id = $1`, [chargeId]);
            // Also clear the scenario's fixture rows to keep DB tidy
            await client.query(`DELETE FROM ${qualifyTable('documents')} WHERE charge_id = $1`, [chargeId]);
            await client.query(`DELETE FROM ${qualifyTable('transactions')} WHERE charge_id = $1`, [chargeId]);
            await client.query(`DELETE FROM ${qualifyTable('charges')} WHERE id = $1`, [chargeId]);
            // Clean up fixture-specific entities to prevent count inflation
            await client.query(`DELETE FROM ${qualifyTable('financial_accounts_tax_categories')} 
         WHERE tax_category_id IN ($1, $2)`, [makeUUID('tax-category', 'expense-general'), makeUUID('tax-category', 'bank-account-tax-category')]);
            await client.query(`DELETE FROM ${qualifyTable('financial_accounts')} WHERE account_number = $1`, ['BANK-ACCOUNT-001']);
            await client.query(`DELETE FROM ${qualifyTable('tax_categories')} WHERE id IN ($1, $2)`, [makeUUID('tax-category', 'expense-general'), makeUUID('tax-category', 'bank-account-tax-category')]);
            await client.query(`DELETE FROM ${qualifyTable('businesses')} WHERE id IN ($1, $2)`, [makeUUID('business', 'admin-business-scenario-a'), makeUUID('business', 'supplier-local-ltd')]);
            await client.query(`DELETE FROM ${qualifyTable('financial_entities')} WHERE id IN ($1, $2, $3, $4)`, [
                makeUUID('business', 'admin-business-scenario-a'),
                makeUUID('business', 'supplier-local-ltd'),
                makeUUID('tax-category', 'expense-general'),
                makeUUID('tax-category', 'bank-account-tax-category'),
            ]);
        }
        finally {
            client.release();
        }
    });
    it('should generate balanced ledger records for ILS receipt expense', async () => {
        // Insert fixture within an explicit transaction and COMMIT to persist rows.
        const insertClient = await db.getPool().connect();
        try {
            await insertClient.query('BEGIN');
            const adminContext = await buildAdminContextFromDb(insertClient);
            await insertFixture(insertClient, expenseScenarioA, adminContext.ownerId);
            // Reset RLS context to the charge owner before verification 
            // (insertFixture might leave it set to the last inserted entity's owner)
            const chargeOwnerId = makeUUID('business', 'admin-business-scenario-a');
            await insertClient.query("SELECT set_config('app.current_business_id', $1, true)", [chargeOwnerId]);
            const chargeId = makeUUID('charge', 'charge-office-supplies');
            // Verify inserts within the same transaction
            const chargeResult = await insertClient.query(`SELECT * FROM ${qualifyTable('charges')} WHERE id = $1`, [chargeId]);
            expect(chargeResult.rows).toHaveLength(1);
            const transactionsResult = await insertClient.query(`SELECT * FROM ${qualifyTable('transactions')} WHERE charge_id = $1`, [chargeId]);
            expect(transactionsResult.rows).toHaveLength(1);
            expect(transactionsResult.rows[0].amount).toBe('-500.00');
            expect(transactionsResult.rows[0].currency).toBe(Currency.Ils);
            const documentsResult = await insertClient.query(`SELECT * FROM ${qualifyTable('documents')} WHERE charge_id = $1`, [chargeId]);
            expect(documentsResult.rows).toHaveLength(1);
            expect(documentsResult.rows[0].type).toBe('RECEIPT');
            expect(Number(documentsResult.rows[0].total_amount)).toBe(500.0);
            await insertClient.query('COMMIT');
        }
        catch (e) {
            try {
                await insertClient.query('ROLLBACK');
            }
            catch { }
            throw e;
        }
        finally {
            insertClient.release();
        }
        // Re-open a new client (outside prior transaction) for verification & generation
        const client = await db.getPool().connect();
        try {
            const adminContext = await buildAdminContextFromDb(client);
            // Set context to allow reading protected rows
            const fixtureOwnerId = makeUUID('business', 'admin-business-scenario-a');
            await client.query("SELECT set_config('app.current_business_id', $1, false)", [fixtureOwnerId]);
            const chargeId = makeUUID('charge', 'charge-office-supplies');
            const chargeResult = await client.query(`SELECT * FROM ${qualifyTable('charges')} WHERE id = $1`, [chargeId]);
            const charge = chargeResult.rows[0];
            const context = createLedgerTestContext({
                pool: db.getPool(),
                env,
                moduleId: 'ledger',
                businessId: adminContext.ownerId,
            });
            const result = await ledgerGenerationByCharge(charge, { insertLedgerRecordsIfNotExists: true }, context, {});
            // Verify result structure and success
            expect(result).toBeDefined();
            expect(result).not.toBeNull();
            // Type guard: ensure result is LedgerRecordsProto, not CommonError
            if (!result || 'message' in result) {
                throw new Error(`Ledger generation failed: ${result ? result.message : 'null result'}`);
            }
            expect(result.errors).toEqual([]);
            expect(result.balance?.isBalanced).toBe(true);
            expect(result.records).toHaveLength(2);
            // Query ledger records from database
            const ledgerResult = await client.query(`SELECT * FROM ${qualifyTable('ledger_records')} WHERE charge_id = $1 ORDER BY created_at ASC`, [charge.id]);
            // High-level semantic assertions using helper
            const ledgerExpectation = expenseScenarioA.expectations?.ledger?.[0];
            assertSimpleLocalExpenseScenario(ledgerResult.rows, {
                chargeId: charge.id,
                ownerId: charge.owner_id, // Use actual charge owner for ledger record owner assertions
                expenseEntity: makeUUID('tax-category', 'expense-general'),
                bankEntity: makeUUID('tax-category', 'bank-account-tax-category'),
                expectedCurrency: Currency.Ils,
                expectedTotal: ledgerExpectation?.totalDebitLocal || 500.0,
            });
            // Audit trail assertions (timestamps, lock state)
            assertAuditTrail(ledgerResult.rows);
            // Basic record count sanity check retained
            expect(ledgerResult.rows.length).toBe(ledgerExpectation?.recordCount || 2);
            console.log('✅ Ledger generation semantic assertions passed for Scenario A');
        }
        finally {
            client.release();
        }
    });
    it('should be idempotent on repeated ledger generation (no duplicate records)', async () => {
        const client = await db.getPool().connect();
        try {
            const chargeId = makeUUID('charge', 'charge-office-supplies');
            const adminId = makeUUID('business', 'admin-business-scenario-a');
            // Set context to allow reading protected rows
            await client.query("SELECT set_config('app.current_business_id', $1, false)", [adminId]);
            // Ensure fixture inserted (if previous test did cleanup, reinsert minimal fixture)
            const existingCharge = await client.query(`SELECT * FROM ${qualifyTable('charges')} WHERE id = $1`, [chargeId]);
            const adminContext = await buildAdminContextFromDb(client);
            if (existingCharge.rows.length === 0) {
                // Reinsert fixture quickly
                await client.query('BEGIN');
                await insertFixture(client, expenseScenarioA, adminContext.ownerId);
                await client.query('COMMIT');
            }
            const chargeResult = await client.query(`SELECT * FROM ${qualifyTable('charges')} WHERE id = $1`, [chargeId]);
            const charge = chargeResult.rows[0];
            const context = createLedgerTestContext({
                pool: db.getPool(),
                env,
                moduleId: 'ledger',
                businessId: adminContext.ownerId,
            });
            // First generation
            const first = await ledgerGenerationByCharge(charge, { insertLedgerRecordsIfNotExists: true }, context, {});
            if (!first || 'message' in first) {
                throw new Error('First generation failed');
            }
            const firstRecords = await client.query(`SELECT id FROM ${qualifyTable('ledger_records')} WHERE charge_id = $1 ORDER BY id ASC`, [charge.id]);
            const firstIds = firstRecords.rows.map(r => r.id);
            // Second generation with insertion disabled (should not create duplicates)
            const second = await ledgerGenerationByCharge(charge, { insertLedgerRecordsIfNotExists: false }, context, {});
            if (!second || 'message' in second) {
                throw new Error('Second generation failed');
            }
            const secondRecords = await client.query(`SELECT id FROM ${qualifyTable('ledger_records')} WHERE charge_id = $1 ORDER BY id ASC`, [charge.id]);
            const secondIds = secondRecords.rows.map(r => r.id);
            expect(secondIds.length).toBe(firstIds.length);
            expect(secondIds).toEqual(firstIds);
        }
        finally {
            client.release();
        }
    });
    it('should validate fixture structure matches expectations', () => {
        // Verify fixture has expected structure
        expect(expenseScenarioA.businesses?.businesses).toHaveLength(2); // Admin + supplier
        expect(expenseScenarioA.taxCategories?.taxCategories).toHaveLength(2); // Expense + bank
        expect(expenseScenarioA.charges?.charges).toHaveLength(1);
        expect(expenseScenarioA.transactions?.transactions).toHaveLength(1);
        expect(expenseScenarioA.documents?.documents).toHaveLength(1);
        // Verify expectations are defined
        expect(expenseScenarioA.expectations).toBeDefined();
        expect(expenseScenarioA.expectations?.ledger).toHaveLength(1);
        const ledgerExpectation = expenseScenarioA.expectations?.ledger?.[0];
        expect(ledgerExpectation?.chargeId).toBe(makeUUID('charge', 'charge-office-supplies'));
        expect(ledgerExpectation?.recordCount).toBe(2);
        expect(ledgerExpectation?.balanced).toBe(true);
        expect(ledgerExpectation?.totalDebitLocal).toBe(500.0);
        expect(ledgerExpectation?.totalCreditLocal).toBe(500.0);
        expect(ledgerExpectation?.debitEntities).toHaveLength(1);
        expect(ledgerExpectation?.creditEntities).toHaveLength(1);
    });
    it('should create deterministic UUIDs for reproducible tests', () => {
        // Verify all IDs are deterministic and match expected values
        const adminBusinessId = makeUUID('business', 'admin-business-scenario-a');
        const supplierId = makeUUID('business', 'supplier-local-ltd');
        const chargeId = makeUUID('charge', 'charge-office-supplies');
        const transactionId = makeUUID('transaction', 'transaction-supplies-payment');
        const documentId = makeUUID('document', 'document-supplies-receipt');
        expect(adminBusinessId).toBe(expenseScenarioA.businesses?.businesses[0]?.id);
        expect(supplierId).toBe(expenseScenarioA.businesses?.businesses[1]?.id);
        expect(chargeId).toBe(expenseScenarioA.charges?.charges[0]?.id);
        expect(transactionId).toBe(expenseScenarioA.transactions?.transactions[0]?.id);
        expect(documentId).toBe(expenseScenarioA.documents?.documents[0]?.id);
    });
});
//# sourceMappingURL=ledger-scenario-a.integration.test.js.map