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

class MockLifecycleExchange implements IExchangeConnector {
    private positionQueue: Array<any | null> = [];
    private latestPrice = 100;

    queuePositions(...positions: Array<any | null>) {
        this.positionQueue.push(...positions);
    }

    setLatestPrice(price: number) {
        this.latestPrice = price;
    }

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

    async placeOrder(_symbol: string, _side: 'buy' | 'sell', qty: number, _type: 'market' | 'limit', price?: number): Promise<any> {
        return {
            id: `mock-order-${Date.now()}`,
            status: 'filled',
            filled_avg_price: price || this.latestPrice,
            filled_qty: qty
        };
    }

    async getPosition(_symbol: string): Promise<any> {
        if (this.positionQueue.length > 0) {
            return this.positionQueue.shift();
        }
        return {
            side: 'long',
            qty: '1',
            avg_entry_price: this.latestPrice
        };
    }
    cancelOrder = async (_orderId: string, _symbol?: string): Promise<boolean> => true;
    getCapabilities = () => ({
        fetchOpenOrders: true,
        fetchOrders: true,
        fetchClosedOrders: true,
        fetchMyTrades: true,
        fetchOHLCV: true,
        cancelOrder: true,
        createOrder: true,
        editOrder: false,
        fetchBalance: true,
        fetchLedger: false,
        fetchTicker: true,
        fetchTickers: true,
        fetchPosition: true,
        fetchPositions: true,
        shorting: true
    });
}

class MockTradeExecutorForMonitor {
    public positions = new Map<string, any>();
    public exitCalls: Array<{ symbol: string; reason: string; price: number; tradeId?: string }> = [];
    public completeCalls: Array<{ symbol: string; reason: string; price: number; tradeId?: string }> = [];
    public manualReviewCalls: Array<{ symbol: string; reason: string; details?: string; tradeId?: string }> = [];
    public pendingOrders = new Map<string, any>();

    getActiveSymbols(): string[] {
        return Array.from(this.positions.keys());
    }

    getActivePosition(symbol: string, tradeId?: string): any {
        const bySymbol = this.positions.get(symbol) || [];
        if (!Array.isArray(bySymbol) || bySymbol.length === 0) return null;
        if (tradeId) {
            return bySymbol.find((p: any) => p.tradeId === tradeId) || null;
        }
        return bySymbol[0] || null;
    }

    getActivePositions(symbol: string): any[] {
        const bySymbol = this.positions.get(symbol) || [];
        return Array.isArray(bySymbol) ? bySymbol : [];
    }

    async executeExit(symbol: string, currentPrice: number, reason: string, tradeId?: string): Promise<{ success: boolean }> {
        this.exitCalls.push({ symbol, reason, price: currentPrice, tradeId });
        return { success: true };
    }

    async markTradeComplete(symbol: string, exitPrice: number, reason: string, tradeId?: string): Promise<{ success: boolean }> {
        this.completeCalls.push({ symbol, reason, price: exitPrice, tradeId });
        return { success: true };
    }

    markExitManualReview(symbol: string, reason: string, details?: string, tradeId?: string): void {
        this.manualReviewCalls.push({ symbol, reason, details, tradeId });
    }

    getAllActivePositions(): any[] {
        return [];
    }

    getProfileId(): string {
        return 'test-profile';
    }

    getPendingOrders(): Map<string, any> {
        return this.pendingOrders;
    }

    verifyCapability(_capability: string, _reason: string): boolean {
        return true;
    }
}


async function testZeroTakeProfitGuard() {
    const exchange = new MockLifecycleExchange();
    exchange.setLatestPrice(105);

    const executor = new MockTradeExecutorForMonitor();
    executor.positions.set('BTC/USD', [{
        symbol: 'BTC/USD',
        side: 'BUY',
        entryPrice: 100,
        size: 1,
        stopLoss: 0,
        takeProfit: 0,
        peakPrice: 100,
        profitGuardActive: false
    }]);

    const monitor = new TradeMonitor(exchange, executor as any);
    await (monitor as any).checkOpenPositions();

    assert.equal(executor.exitCalls.length, 0, 'Zero takeProfit must not activate trailing guard / exit.');
    const posAfter = executor.getActivePosition('BTC/USD');
    assert.equal(Boolean(posAfter?.profitGuardActive), false, 'profitGuardActive must remain false when takeProfit is zero.');
}

async function testPartialExitReduction() {
    const exchange = new MockLifecycleExchange();

    // Avoid DB writes in test mode.
    (supabaseService as any).logTransaction = async () => { };

    const executor = new TradeExecutor(exchange, undefined, 'global', 'test-profile');
    const activeMap = (executor as any).activeTraders as Map<string, any>;
    activeMap.set('ETH/USD::TRD-test-partial', {
        symbol: 'ETH/USD',
        side: SignalDirection.BUY,
        entryPrice: 2000,
        size: 1,
        stopLoss: 1900,
        takeProfit: 2200,
        peakPrice: 2000,
        userId: 'global',
        profileId: 'test-profile',
        tradeId: 'TRD-test-partial'
    });

    const partial = await executor.applyExitFill('ETH/USD', 2100, 0.4, 'Regression Partial Exit');
    assert.equal(partial.success, true, 'Partial exit should apply successfully.');
    assert.equal(partial.fullyClosed, false, 'Partial exit must not fully close the trade.');
    assert.equal(partial.remainingSize, 0.6, 'Remaining size must be reduced immediately after partial exit.');
    assert.equal(executor.getActivePosition('ETH/USD')?.size, 0.6, 'Active position size should be reduced in-memory.');

    const final = await executor.applyExitFill('ETH/USD', 2110, 0.6, 'Regression Final Exit');
    assert.equal(final.success, true, 'Final exit should apply successfully.');
    assert.equal(final.fullyClosed, true, 'Second fill should close remaining quantity.');
    assert.equal(executor.getActivePosition('ETH/USD'), null, 'Position must be removed after full exit fill.');
}

async function testStalePositionReconciliation() {
    const previousStrictEvidenceSetting = config.REQUIRE_EXCHANGE_FILL_EVIDENCE_FOR_AUTO_CLOSE;
    (config as any).REQUIRE_EXCHANGE_FILL_EVIDENCE_FOR_AUTO_CLOSE = true;

    try {
        const exchange = new MockLifecycleExchange();
        exchange.setLatestPrice(99);
        exchange.queuePositions(
            null, // first scan: miss #1
            null, // second scan: miss #2
            null  // second scan confirmation: still missing
        );

        const executor = new MockTradeExecutorForMonitor();
        executor.positions.set('SOL/USD', [{
            symbol: 'SOL/USD',
            side: 'BUY',
            entryPrice: 100,
            size: 2,
            stopLoss: 90,
            takeProfit: 110,
            peakPrice: 100
        }]);

        const monitor = new TradeMonitor(exchange, executor as any);
        await (monitor as any).checkOpenPositions();
        assert.equal(executor.completeCalls.length, 0, 'Single missing detection must not finalize trade.');

        await (monitor as any).checkOpenPositions();
        assert.equal(executor.completeCalls.length, 0, 'Strict evidence mode must not auto-finalize stale position without exchange fill evidence.');
        assert.equal(executor.manualReviewCalls.length, 1, 'Second confirmed missing detection should route stale position to manual review.');
        assert.equal(executor.manualReviewCalls[0].symbol, 'SOL/USD');
    } finally {
        (config as any).REQUIRE_EXCHANGE_FILL_EVIDENCE_FOR_AUTO_CLOSE = previousStrictEvidenceSetting;
    }
}

async function run() {
    await testZeroTakeProfitGuard();
    await testPartialExitReduction();
    await testStalePositionReconciliation();
    console.log('[lifecycle-regressions] OK: zero-TP, partial-exit, stale reconciliation checks passed');
}

await run();
