import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { config } from '../src/config/index.js';
import { runBacktest } from '../src/backtest/index.js';
import type { BacktestRequest } from '../src/backtest/types.js';
import logger from '../src/utils/logger.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const repoRoot = path.resolve(__dirname, '..');

const makeCandle = (timestamp: number, close: number) => ({
    timestamp,
    open: close * 0.999,
    high: close * 1.002,
    low: close * 0.998,
    close,
    volume: 100
});

const build15mSeries = (startTs: number, count: number): Array<ReturnType<typeof makeCandle>> => {
    const out: Array<ReturnType<typeof makeCandle>> = [];
    let price = 100;
    for (let i = 0; i < count; i++) {
        if (i < Math.floor(count * 0.6)) {
            price += 0.5;
        } else {
            price -= 0.65;
        }
        out.push(makeCandle(startTs + i * 15 * 60 * 1000, Number(price.toFixed(6))));
    }
    return out;
};

const buildRequest = (): BacktestRequest => {
    const candles = build15mSeries(Date.UTC(2025, 0, 1, 0, 0, 0), 700);
    const from = new Date(candles[220].timestamp).toISOString();
    const to = new Date(candles[candles.length - 1].timestamp).toISOString();

    return {
        mode: 'backtest',
        symbols: ['BTC/USDT'],
        timeframe: '15m',
        dateRange: { from, to },
        dataSource: {
            type: 'json',
            payload: {
                candles: {
                    'BTC/USDT': {
                        '15m': candles
                    }
                }
            }
        },
        strategyConfig: {
            rules: [
                { ruleId: 'TrendBiasRule', enabled: true, ruleType: 'voting', params: { fastPeriod: 10, slowPeriod: 20 } },
                { ruleId: 'SessionRule', enabled: true, ruleType: 'mandatory', params: { sessions: '24/7' } },
                { ruleId: 'RiskManagementRule', enabled: true, ruleType: 'mandatory', params: { atrPeriod: 14, maxRisk: 10 } }
            ],
            riskLimits: {
                maxDailyLossUsd: 5000,
                dailyProfitTargetUsd: 5000,
                maxOpenTrades: 3,
                maxConsecutiveLosses: 10
            },
            execution: {
                orderType: 'market',
                cooldownMinutes: 0,
                minRulePassRatio: 1,
                entryMode: 'both'
            }
        },
        execution: {
            initialCapitalUsd: 10_000,
            orderType: 'market',
            slippageBps: 5,
            partialFillPct: 1,
            enforceWarmup: true,
            allowNegativeCash: false,
            forceCloseAtWindowEnd: false
        }
    };
};

const verifySourceIsolation = (): void => {
    const backtestRoot = path.join(repoRoot, 'src', 'backtest');
    const files = fs.readdirSync(backtestRoot, { recursive: true })
        .filter((entry) => String(entry).endsWith('.ts'))
        .map((entry) => path.join(backtestRoot, String(entry)));
    assert(files.length > 0, 'Expected backtest source files to exist.');

    const forbiddenPatterns = [
        /from ['"]\.\.\/services\/TradeExecutor/i,
        /from ['"]\.\.\/services\/AutoTrader/i,
        /from ['"]\.\.\/services\/CapitalLedger/i,
        /from ['"]\.\.\/connectors\/alpaca/i,
        /from ['"]\.\.\/connectors\/ccxt/i,
        /\.from\(['"](orders|trade_history|capital_ledgers)['"]\)/
    ];

    for (const filePath of files) {
        const source = fs.readFileSync(filePath, 'utf8');
        for (const pattern of forbiddenPatterns) {
            assert(!pattern.test(source), `Isolation violation in ${path.relative(repoRoot, filePath)} (${pattern})`);
        }
    }
};

const verifyBacktestBehavior = async (): Promise<void> => {
    const originalFlag = config.ENABLE_BACKTEST;
    const originalInfo = (logger as any).info?.bind(logger);
    const originalWarn = (logger as any).warn?.bind(logger);
    const originalError = (logger as any).error?.bind(logger);
    config.ENABLE_BACKTEST = true;
    (logger as any).info = () => undefined;
    (logger as any).warn = () => undefined;
    (logger as any).error = () => undefined;
    try {
        const request = buildRequest();
        const first = await runBacktest(request, {
            profileSettings: {
                allocated_capital: 10_000,
                risk_per_trade_percent: 1,
                strategy_config: request.strategyConfig
            }
        });
        const second = await runBacktest(request, {
            profileSettings: {
                allocated_capital: 10_000,
                risk_per_trade_percent: 1,
                strategy_config: request.strategyConfig
            }
        });

        assert.deepEqual(first, second, 'Backtest must be deterministic for identical inputs.');
        const fromTs = Date.parse(request.dateRange.from);
        const toTs = Date.parse(request.dateRange.to);
        assert.equal(first.window.endOfWindowPolicy, 'OPEN_AT_END', 'Default end-of-window behavior must be OPEN_AT_END.');
        assert.equal(first.window.fromTimestamp, fromTs, 'Window.fromTimestamp must match request.from.');
        assert.equal(first.window.toTimestamp, toTs, 'Window.toTimestamp must match request.to.');
        assert(first.window.includesWarmupCandles, 'Warm-up candles should be loaded before replay start.');
        assert(first.warmup.endTimestamp >= first.warmup.startTimestamp, 'Warm-up report must be valid.');
        assert(first.trades.every((trade) => trade.entryTimestamp >= fromTs), 'No trades allowed before replay window start.');
        assert(first.trades.every((trade) => trade.entryTimestamp <= toTs), 'No trades allowed after replay window end.');
        assert(first.trades.every((trade) => trade.exitTimestamp <= toTs), 'No exits allowed after replay window end.');
        assert(first.trades.every((trade) => trade.entryTimestamp >= first.warmup.endTimestamp), 'No trades allowed before warm-up completion.');
        assert(first.timeline.every((point) => point.cashUsd >= -1e-6), 'Cash must never be negative with allowNegativeCash=false.');
        assert(first.timeline.every((point) => point.reservedUsd <= point.cashUsd + 1e-6), 'Reserved capital cannot exceed cash.');
        assert.equal(first.diagnostics.connectorCalls.placeOrder, 0, 'Backtest must never call placeOrder.');

        const forcedWindowCloseRequest: BacktestRequest = {
            ...request,
            execution: {
                ...(request.execution || {}),
                forceCloseAtWindowEnd: true
            }
        };
        const forcedCloseResult = await runBacktest(forcedWindowCloseRequest, {
            profileSettings: {
                allocated_capital: 10_000,
                risk_per_trade_percent: 1,
                strategy_config: request.strategyConfig
            }
        });
        assert.equal(forcedCloseResult.window.endOfWindowPolicy, 'FORCE_CLOSE', 'Force-close mode must be reported in window metadata.');
        assert.equal(forcedCloseResult.openPositionsAtEnd.length, 0, 'Force-close mode should not leave OPEN_AT_END positions.');
        assert.equal(forcedCloseResult.diagnostics.connectorCalls.placeOrder, 0, 'Force-close mode must never call placeOrder.');
    } finally {
        config.ENABLE_BACKTEST = originalFlag;
        (logger as any).info = originalInfo;
        (logger as any).warn = originalWarn;
        (logger as any).error = originalError;
    }
};

verifySourceIsolation();
await verifyBacktestBehavior();
console.log('[backtest-isolation] OK: warm-up, capital, determinism, and isolation guards validated');
