import logger from '../utils/logger.js';
import { config } from '../config/index.js';
import { getTradeProfileCapital } from './profileRepository.js';
import { getCapitalLedger, upsertCapitalLedger } from './capitalLedgerRepository.js';

export interface CapitalLedgerRecord {
    profile_id: string;
    allocated_capital: number;
    reserved_for_orders: number;
    reserved_for_positions: number;
    realized_pnl: number;
    updated_at: string;
}

const toNumeric = (value: unknown): number => {
    const numeric = Number(value);
    if (!Number.isFinite(numeric)) return 0;
    return numeric;
};

export class CapitalLedger {
    private locks = new Map<string, Promise<unknown>>();

    private async withLock<T>(profileId: string, work: () => Promise<T>): Promise<T | null> {
        const key = profileId || 'global';
        const previous = this.locks.get(key) ?? Promise.resolve();
        const next = previous.then(() => work()).finally(() => {
            if (this.locks.get(key) === next) {
                this.locks.delete(key);
            }
        });
        this.locks.set(key, next);
        return next;
    }

    private async ensureLedger(profileId: string, allocatedCapital?: number): Promise<CapitalLedgerRecord | null> {
        try {
            const profileCapital = await getTradeProfileCapital(profileId);
            const allocation = toNumeric(allocatedCapital ?? profileCapital?.allocatedCapital ?? config.TOTAL_CAPITAL);
            const existing = await getCapitalLedger(profileId);
            const nextRecord: CapitalLedgerRecord = {
                profile_id: profileId,
                allocated_capital: allocation,
                reserved_for_orders: toNumeric(existing?.reserved_for_orders),
                reserved_for_positions: toNumeric(existing?.reserved_for_positions),
                realized_pnl: toNumeric(existing?.realized_pnl),
                updated_at: new Date().toISOString()
            };

            return await upsertCapitalLedger(nextRecord);
        } catch (err: any) {
            if (this.isRpcNetworkFailure(err)) {
                logger.error(`[CapitalLedger] ensureLedger network failure for ${profileId}, aborting ledger mutation (fail-closed): ${err.message}`);
                return null;
            }
            logger.error(`[CapitalLedger] ensureLedger unexpected error: ${err.message}`);
            return null;
        }
    }

    private async mutate(
        profileId: string,
        fn: (current: CapitalLedgerRecord) => Promise<CapitalLedgerRecord | null>
    ) {
        return this.withLock(profileId, async () => {
            const ledger = await this.ensureLedger(profileId);
            if (!ledger) return null;
            return fn(ledger);
        });
    }

    public async getLedger(profileId: string): Promise<CapitalLedgerRecord | null> {
        return getCapitalLedger(profileId);
    }

    public async reserveForOrder(profileId: string, amount: number): Promise<boolean> {
        if (!profileId || amount <= 0) return false;
        const result = await this.mutate(profileId, async (ledger) => {
            const available = this.availableCapital(ledger);
            if (available + 1e-8 < amount) {
                return null;
            }

            return upsertCapitalLedger({
                ...ledger,
                reserved_for_orders: toNumeric(ledger.reserved_for_orders) + amount,
                updated_at: new Date().toISOString()
            });
        });
        if (result) return true;

        const ledger = await this.getLedger(profileId);
        if (!ledger) return false;

        const available = this.availableCapital(ledger);
        if (available + 1e-8 >= amount) {
            logger.error(
                `[CapitalLedger] reserveForOrder parity mismatch for ${profileId}: RPC rejected amount=${amount.toFixed(8)} despite available=${available.toFixed(8)}. ` +
                `This usually indicates fn_reserve_for_order is stale and does not include realized_pnl in its gate.`
            );
        } else {
            logger.warn(
                `[CapitalLedger] reserveForOrder rejected for ${profileId}: requested=${amount.toFixed(8)}, available=${available.toFixed(8)}.`
            );
        }
        return false;
    }

    public async releaseOrderReservation(profileId: string, amount: number): Promise<void> {
        if (!profileId || amount <= 0) return;
        await this.mutate(profileId, async (ledger) => {
            return upsertCapitalLedger({
                ...ledger,
                reserved_for_orders: Math.max(0, toNumeric(ledger.reserved_for_orders) - amount),
                updated_at: new Date().toISOString()
            });
        });
    }

    public async adjustPositionReservation(profileId: string, delta: number): Promise<void> {
        if (!profileId || delta === 0) return;
        await this.mutate(profileId, async (ledger) => {
            return upsertCapitalLedger({
                ...ledger,
                reserved_for_positions: Math.max(0, toNumeric(ledger.reserved_for_positions) + delta),
                updated_at: new Date().toISOString()
            });
        });
    }

    public async recordRealizedPnl(profileId: string, delta: number): Promise<void> {
        if (!profileId || delta === 0) return;
        await this.mutate(profileId, async (ledger) => {
            return upsertCapitalLedger({
                ...ledger,
                realized_pnl: toNumeric(ledger.realized_pnl) + delta,
                updated_at: new Date().toISOString()
            });
        });
    }

    public async rebuildLedger(profileId: string, reservedOrders: number, reservedPositions: number): Promise<void> {
        if (!profileId) return;
        await this.withLock(profileId, async () => {
            const ledger = await this.ensureLedger(profileId);
            if (!ledger) return null;
            return upsertCapitalLedger({
                ...ledger,
                reserved_for_orders: Math.max(0, toNumeric(reservedOrders)),
                reserved_for_positions: Math.max(0, toNumeric(reservedPositions)),
                updated_at: new Date().toISOString()
            });
        });
    }

    public async getAvailableCapital(profileId: string): Promise<number | null> {
        if (!profileId) return null;
        const ledger = await this.ensureLedger(profileId);
        if (!ledger) return null;
        return this.availableCapital(ledger);
    }

    public availableCapital(record: CapitalLedgerRecord) {
        return record.allocated_capital - record.reserved_for_orders - record.reserved_for_positions + record.realized_pnl;
    }

    private isRpcNetworkFailure(error: any): boolean {
        const message = String(error?.message || '').toLowerCase();
        return message.includes('fetch failed') || message.includes('network');
    }
}

export const capitalLedger = new CapitalLedger();
