import assert from 'node:assert/strict';
import { config } from '../src/config/index.js';
import type { Candle, IExchangeConnector } from '../src/connectors/types.js';
import { TradeExecutor } from '../src/services/TradeExecutor.js';
import { capitalLedger } from '../src/services/CapitalLedger.js';
import { distributedLockService } from '../src/services/distributedLockService.js';
import { supabaseService } from '../src/services/SupabaseService.js';
import { SignalDirection } from '../src/strategies/rules/types.js';

class MockExchange implements IExchangeConnector {
    public placedQty = 0;
    public placedPrice = 0;
    private orderSeq = 0;

    getCapabilities() {
        return {
            fetchOpenOrders: true,
            fetchClosedOrders: true,
            shorting: true
        };
    }

    async fetchOHLCV(_symbol: string, _timeframe: string, _limit?: number): Promise<Candle[]> {
        return [{
            timestamp: Date.now(),
            open: 100,
            high: 100,
            low: 100,
            close: 100,
            volume: 1
        }];
    }

    async placeOrder(
        symbol: string,
        side: 'buy' | 'sell',
        qty: number,
        _type: 'market' | 'limit',
        price?: number
    ): Promise<any> {
        this.orderSeq += 1;
        this.placedQty = Number(qty);
        this.placedPrice = Number(price || 100);
        return {
            id: `strict-cap-${this.orderSeq}`,
            symbol,
            side,
            qty,
            status: 'filled',
            filled_avg_price: this.placedPrice,
            filled_qty: String(qty)
        };
    }

    async getPosition(_symbol: string): Promise<any> {
        return null;
    }

    async getOrder(orderId: string): Promise<any> {
        return {
            id: orderId,
            status: 'filled',
            filled_avg_price: 100,
            filled_qty: String(this.placedQty || 1)
        };
    }
}

const saveConfig = () => ({
    ENTRY_CAPITAL_BUFFER_PCT: config.ENTRY_CAPITAL_BUFFER_PCT,
    ENABLE_STRICT_CAPITAL_GUARD: config.ENABLE_STRICT_CAPITAL_GUARD,
    STRICT_CAPITAL_SLIPPAGE_BUFFER_PCT: config.STRICT_CAPITAL_SLIPPAGE_BUFFER_PCT,
    STRICT_CAPITAL_FEE_BUFFER_PCT: config.STRICT_CAPITAL_FEE_BUFFER_PCT,
    STRICT_CAPITAL_MIN_RESERVE_USD: config.STRICT_CAPITAL_MIN_RESERVE_USD
});

type Stubbed = {
    hasActiveOrderForTradeId: any;
    hasFinalizedTradeHistory: any;
    getVirtualOpenPosition: any;
    getPendingOrdersForProfile: any;
    updateOrderStatus: any;
    logOrder: any;
    logTransaction: any;
    reserveForOrder: any;
    releaseOrderReservation: any;
    adjustPositionReservation: any;
    recordRealizedPnl: any;
    getAvailableCapital: any;
    tryAcquireRowLock: any;
    releaseRowLock: any;
    isEntryInProgress: any;
};

const stubDependencies = (availableCapital: number, reservationLog: number[]): Stubbed => {
    const originals: Stubbed = {
        hasActiveOrderForTradeId: (supabaseService as any).hasActiveOrderForTradeId,
        hasFinalizedTradeHistory: (supabaseService as any).hasFinalizedTradeHistory,
        getVirtualOpenPosition: (supabaseService as any).getVirtualOpenPosition,
        getPendingOrdersForProfile: (supabaseService as any).getPendingOrdersForProfile,
        updateOrderStatus: (supabaseService as any).updateOrderStatus,
        logOrder: (supabaseService as any).logOrder,
        logTransaction: (supabaseService as any).logTransaction,
        reserveForOrder: (capitalLedger as any).reserveForOrder,
        releaseOrderReservation: (capitalLedger as any).releaseOrderReservation,
        adjustPositionReservation: (capitalLedger as any).adjustPositionReservation,
        recordRealizedPnl: (capitalLedger as any).recordRealizedPnl,
        getAvailableCapital: (capitalLedger as any).getAvailableCapital,
        tryAcquireRowLock: (distributedLockService as any).tryAcquireRowLock,
        releaseRowLock: (distributedLockService as any).releaseRowLock,
        isEntryInProgress: (distributedLockService as any).isEntryInProgress
    };

    (supabaseService as any).hasActiveOrderForTradeId = async () => false;
    (supabaseService as any).hasFinalizedTradeHistory = async () => false;
    (supabaseService as any).getVirtualOpenPosition = async () => null;
    (supabaseService as any).getPendingOrdersForProfile = async () => [];
    (supabaseService as any).updateOrderStatus = async () => { };
    (supabaseService as any).logOrder = async () => { };
    (supabaseService as any).logTransaction = async () => { };

    (capitalLedger as any).getAvailableCapital = async () => availableCapital;
    (capitalLedger as any).reserveForOrder = async (_profileId: string, amount: number) => {
        reservationLog.push(Number(amount));
        return true;
    };
    (capitalLedger as any).releaseOrderReservation = async () => { };
    (capitalLedger as any).adjustPositionReservation = async () => { };
    (capitalLedger as any).recordRealizedPnl = async () => { };

    (distributedLockService as any).tryAcquireRowLock = async () => true;
    (distributedLockService as any).releaseRowLock = async () => true;
    (distributedLockService as any).isEntryInProgress = async () => false;

    return originals;
};

const restoreDependencies = (originals: Stubbed) => {
    (supabaseService as any).hasActiveOrderForTradeId = originals.hasActiveOrderForTradeId;
    (supabaseService as any).hasFinalizedTradeHistory = originals.hasFinalizedTradeHistory;
    (supabaseService as any).getVirtualOpenPosition = originals.getVirtualOpenPosition;
    (supabaseService as any).getPendingOrdersForProfile = originals.getPendingOrdersForProfile;
    (supabaseService as any).updateOrderStatus = originals.updateOrderStatus;
    (supabaseService as any).logOrder = originals.logOrder;
    (supabaseService as any).logTransaction = originals.logTransaction;

    (capitalLedger as any).reserveForOrder = originals.reserveForOrder;
    (capitalLedger as any).releaseOrderReservation = originals.releaseOrderReservation;
    (capitalLedger as any).adjustPositionReservation = originals.adjustPositionReservation;
    (capitalLedger as any).recordRealizedPnl = originals.recordRealizedPnl;
    (capitalLedger as any).getAvailableCapital = originals.getAvailableCapital;

    (distributedLockService as any).tryAcquireRowLock = originals.tryAcquireRowLock;
    (distributedLockService as any).releaseRowLock = originals.releaseRowLock;
    (distributedLockService as any).isEntryInProgress = originals.isEntryInProgress;
};

const apiServerStub = {
    updatePositions: () => { },
    updateOrders: () => { },
    addHistory: () => { },
    recordOrderFailure: () => { },
    getState: () => ({ accountSnapshot: { buying_power: 10_000 } }),
    updateAccountSnapshot: () => { },
    updateAccountSnapshotCache: () => { }
};

async function runCase(strictEnabled: boolean): Promise<{ placedQty: number; reservedAmount: number }> {
    const originalConfig = saveConfig();
    const reservations: number[] = [];
    const originals = stubDependencies(100, reservations);

    try {
        config.ENTRY_CAPITAL_BUFFER_PCT = 0;
        config.ENABLE_STRICT_CAPITAL_GUARD = strictEnabled;
        config.STRICT_CAPITAL_SLIPPAGE_BUFFER_PCT = 1;
        config.STRICT_CAPITAL_FEE_BUFFER_PCT = 0.15;
        config.STRICT_CAPITAL_MIN_RESERVE_USD = 0;

        const exchange = new MockExchange();
        const executor = new TradeExecutor(
            exchange,
            apiServerStub as any,
            'global',
            strictEnabled ? 'profile-strict-on' : 'profile-strict-off'
        );

        const result = await executor.openPosition('BTC/USD', SignalDirection.BUY, 1, 'limit', 100);
        executor.dispose();

        assert.equal(result.success, true, `Expected entry to succeed (strict=${strictEnabled})`);
        assert(exchange.placedQty > 0, 'Expected exchange order to be placed');
        assert(reservations.length > 0, 'Expected capital reservation to be recorded');

        return {
            placedQty: Number(exchange.placedQty),
            reservedAmount: Number(reservations[0])
        };
    } finally {
        config.ENTRY_CAPITAL_BUFFER_PCT = originalConfig.ENTRY_CAPITAL_BUFFER_PCT;
        config.ENABLE_STRICT_CAPITAL_GUARD = originalConfig.ENABLE_STRICT_CAPITAL_GUARD;
        config.STRICT_CAPITAL_SLIPPAGE_BUFFER_PCT = originalConfig.STRICT_CAPITAL_SLIPPAGE_BUFFER_PCT;
        config.STRICT_CAPITAL_FEE_BUFFER_PCT = originalConfig.STRICT_CAPITAL_FEE_BUFFER_PCT;
        config.STRICT_CAPITAL_MIN_RESERVE_USD = originalConfig.STRICT_CAPITAL_MIN_RESERVE_USD;
        restoreDependencies(originals);
    }
}

async function run() {
    const relaxed = await runCase(false);
    const strict = await runCase(true);

    assert.strictEqual(relaxed.placedQty, 1, 'Without strict guard, qty should use full available notional.');
    assert(
        strict.placedQty < relaxed.placedQty,
        `Strict guard must reduce qty. relaxed=${relaxed.placedQty} strict=${strict.placedQty}`
    );
    assert(
        strict.reservedAmount <= 100 + 1e-6,
        `Strict reservation should remain within available capital envelope. strict=${strict.reservedAmount}`
    );
    assert(
        strict.reservedAmount > (strict.placedQty * 100),
        `Strict reservation should include slippage/fee buffer. qty=${strict.placedQty} reserved=${strict.reservedAmount}`
    );

    console.log('[strict-capital-guard] OK: strict guard reduces qty and raises reservation envelope');
}

await run();
