import logger from '../utils/logger.js';
import { config } from '../config/index.js';
import { supabaseService } from './SupabaseService.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> {
        const client = supabaseService.getClient();
        try {
            if (!client) {
                logger.error(`[CapitalLedger] ensureLedger aborted for ${profileId}: Supabase client unavailable (fail-closed).`);
                return null;
            }
            const profileCapital = await supabaseService.getProfileCapital(profileId);
            const allocation = toNumeric(allocatedCapital ?? profileCapital?.allocatedCapital ?? config.TOTAL_CAPITAL);

            const { data, error } = await client
                .from('capital_ledgers')
                .upsert({
                    profile_id: profileId,
                    allocated_capital: allocation
                }, { onConflict: 'profile_id' })
                .select('*')
                .maybeSingle();

            if (error) {
                if (this.isRpcNetworkFailure(error)) {
                    logger.error(`[CapitalLedger] ensureLedger RPC network failure for ${profileId}, aborting ledger mutation (fail-closed): ${error.message}`);
                    return null;
                }
                logger.error(`[CapitalLedger] ensureLedger failed: ${error.message}`);
                return null;
            }

            return data as CapitalLedgerRecord;
        } 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 rpc<T>(fn: string, args: Record<string, unknown>): Promise<T | null> {
        const client = supabaseService.getClient();
        if (!client) {
            logger.error(`[CapitalLedger] ${fn} aborted: Supabase client unavailable (fail-closed).`);
            return null;
        }
        try {
            const { data, error } = await client.rpc(fn, args);
            if (error) {
                if (this.isRpcNetworkFailure(error)) {
                    logger.error(`[CapitalLedger] ${fn} RPC network failure, rejecting mutation (fail-closed): ${error.message}`);
                    return null;
                }
                logger.error(`[CapitalLedger] ${fn} failed: ${error.message}`);
                return null;
            }
            return data as T;
        } catch (err: any) {
            if (this.isRpcNetworkFailure(err)) {
                logger.error(`[CapitalLedger] ${fn} network failure, rejecting mutation (fail-closed): ${err.message}`);
                return null;
            }
            logger.error(`[CapitalLedger] ${fn} unexpected error: ${err.message}`);
            return null;
        }
    }

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

    public async getLedger(profileId: string): Promise<CapitalLedgerRecord | null> {
        const client = supabaseService.getClient();
        if (!client) return null;
        const { data, error } = await client
            .from('capital_ledgers')
            .select('*')
            .eq('profile_id', profileId)
            .maybeSingle();

        if (error) {
            if (this.isRpcNetworkFailure(error)) {
                logger.error(`[CapitalLedger] getLedger RPC network failure for ${profileId}, returning null (fail-closed): ${error.message}`);
                return null;
            }
            logger.error(`[CapitalLedger] getLedger failed: ${error.message}`);
            return null;
        }

        return data as CapitalLedgerRecord;
    }

    public async reserveForOrder(profileId: string, amount: number): Promise<boolean> {
        if (!profileId || amount <= 0) return false;
        const result = await this.mutate(profileId, async () => {
            return this.rpc<CapitalLedgerRecord>('fn_reserve_for_order', {
                p_profile: profileId,
                p_amount: amount
            });
        });
        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 () => {
            return this.rpc<CapitalLedgerRecord>('fn_release_order_reservation', {
                p_profile: profileId,
                p_amount: amount
            });
        });
    }

    public async adjustPositionReservation(profileId: string, delta: number): Promise<void> {
        if (!profileId || delta === 0) return;
        await this.mutate(profileId, async () => {
            return this.rpc<CapitalLedgerRecord>('fn_adjust_position_reservation', {
                p_profile: profileId,
                p_delta: delta
            });
        });
    }

    public async recordRealizedPnl(profileId: string, delta: number): Promise<void> {
        if (!profileId || delta === 0) return;
        await this.mutate(profileId, async () => {
            return this.rpc<CapitalLedgerRecord>('fn_record_realized_pnl', {
                p_profile: profileId,
                p_delta: delta
            });
        });
    }

    public async rebuildLedger(profileId: string, reservedOrders: number, reservedPositions: number): Promise<void> {
        if (!profileId) return;
        await this.withLock(profileId, async () => {
            await this.ensureLedger(profileId);
            await this.rpc<CapitalLedgerRecord>('fn_rebuild_ledger', {
                p_profile: profileId,
                p_reserved_orders: reservedOrders,
                p_reserved_positions: reservedPositions
            });
        });
    }

    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();
