import 'dotenv/config';
import { createHash, randomUUID } from 'crypto';
import { config, loadDynamicConfig } from '../src/config/index.js';
import { normalizeOrderAction, normalizeTradeSide } from '../src/domain/tradingEnums.js';
import { healthTracker } from '../src/services/healthTracker.js';
import {
    ReconciliationBackfillAuditInsert,
    ReconciliationBackfillOrderInsert,
    supabaseService
} from '../src/services/SupabaseService.js';
import { buildAlpacaSubTag } from '../src/utils/alpacaSubTag.js';

type CliOptions = {
    apply: boolean;
    tradeIds: string[];
};

type TradeSnapshot = {
    profileId: string;
    userId: string;
    tradeId: string;
    symbol: string;
    entrySide: 'BUY' | 'SELL';
    entryQty: number;
    exitQty: number;
    openQty: number;
    entryAvgPrice: number;
};

const EPSILON = 1e-8;
const ORDER_ID_PREFIX = 'MANOVR';

const parseOptions = (argv: string[]): CliOptions => {
    const tradeIds = new Set<string>();
    let apply = false;

    for (const arg of argv) {
        if (arg === '--apply') {
            apply = true;
            continue;
        }
        if (arg.startsWith('--trade=')) {
            const value = String(arg.slice('--trade='.length) || '').trim();
            if (value) tradeIds.add(value);
        }
    }

    return {
        apply,
        tradeIds: Array.from(tradeIds)
    };
};

const toNumber = (value: unknown): number => {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : 0;
};

const expectedExitSide = (entrySide: 'BUY' | 'SELL'): 'BUY' | 'SELL' => {
    return entrySide === 'BUY' ? 'SELL' : 'BUY';
};

const buildManualOverrideOrderId = (profileId: string, tradeId: string): string => {
    const digest = createHash('md5')
        .update(`${profileId}:${tradeId}:manual_override_v1`)
        .digest('hex');
    return `${ORDER_ID_PREFIX}-${digest}`;
};

const buildTradeSnapshots = (rows: any[]): Map<string, TradeSnapshot> => {
    const byTrade = new Map<string, TradeSnapshot>();

    for (const row of rows || []) {
        const tradeId = String(row.trade_id || '').trim();
        const profileId = String(row.profile_id || '').trim();
        if (!tradeId || !profileId) continue;

        const key = `${profileId}::${tradeId}`;
        const qty = toNumber(row.qty ?? row.quantity);
        if (!(qty > EPSILON)) continue;

        const side = normalizeTradeSide(String(row.side || 'BUY'));
        const action = normalizeOrderAction(row.action || undefined);
        const symbol = String(row.symbol || '').trim();
        const userId = String(row.user_id || '').trim();
        const price = toNumber(row.price);

        let snapshot = byTrade.get(key);
        if (!snapshot) {
            snapshot = {
                profileId,
                userId,
                tradeId,
                symbol,
                entrySide: side,
                entryQty: 0,
                exitQty: 0,
                openQty: 0,
                entryAvgPrice: 0
            };
            byTrade.set(key, snapshot);
        }

        const resolvedAction = action || (side === snapshot.entrySide ? 'ENTRY' : 'EXIT');
        if (resolvedAction === 'ENTRY') {
            if (!(snapshot.entryQty > EPSILON)) {
                snapshot.entrySide = side;
            }
            const nextQty = snapshot.entryQty + qty;
            snapshot.entryAvgPrice = nextQty > EPSILON
                ? ((snapshot.entryAvgPrice * snapshot.entryQty) + (price * qty)) / nextQty
                : snapshot.entryAvgPrice;
            snapshot.entryQty = nextQty;
        } else {
            snapshot.exitQty += qty;
        }

        if (!snapshot.symbol && symbol) snapshot.symbol = symbol;
        if (!snapshot.userId && userId) snapshot.userId = userId;
        snapshot.openQty = Number((snapshot.entryQty - snapshot.exitQty).toFixed(8));
    }

    for (const [key, snapshot] of Array.from(byTrade.entries())) {
        if (!(snapshot.openQty > EPSILON)) {
            byTrade.delete(key);
        }
    }

    return byTrade;
};

const run = async (): Promise<void> => {
    const options = parseOptions(process.argv.slice(2));
    if (options.tradeIds.length === 0) {
        throw new Error('Provide at least one --trade=<TRADE_ID>.');
    }

    await loadDynamicConfig(supabaseService);

    healthTracker.recordTradingControl({
        mode: 'PAUSED',
        lastChangedBy: 'maintenance-script',
        lastChangedAt: Date.now(),
        reason: 'Manual override close cycle'
    });

    const client = supabaseService.getClient();
    if (!client) {
        throw new Error('Supabase client is not available.');
    }

    const { data: lifecycleRows, error: lifecycleError } = await client
        .from('orders')
        .select('profile_id,user_id,trade_id,symbol,side,action,qty,quantity,price,status')
        .in('trade_id', options.tradeIds)
        .in('status', ['filled', 'partially_filled', 'partially-filled']);

    if (lifecycleError) {
        throw new Error(`Failed to fetch lifecycle rows: ${lifecycleError.message}`);
    }

    const snapshots = buildTradeSnapshots(lifecycleRows || []);
    const batchId = `MANOVR-BATCH-${randomUUID()}`;
    const nowIso = new Date().toISOString();
    const nowTs = Date.now();

    const candidateOrders: ReconciliationBackfillOrderInsert[] = [];
    const preAuditRows: ReconciliationBackfillAuditInsert[] = [];
    const skipped: Array<Record<string, any>> = [];

    for (const tradeId of options.tradeIds) {
        const matching = Array.from(snapshots.values()).filter((row) => row.tradeId === tradeId);
        if (matching.length === 0) {
            skipped.push({ tradeId, reason: 'trade_not_open_or_not_found' });
            continue;
        }
        if (matching.length > 1) {
            skipped.push({ tradeId, reason: 'ambiguous_trade_multiple_profiles' });
            continue;
        }

        const trade = matching[0];
        if (!(trade.openQty > EPSILON)) {
            skipped.push({ tradeId, reason: 'trade_not_open' });
            continue;
        }

        const orderId = buildManualOverrideOrderId(trade.profileId, trade.tradeId);
        const side = expectedExitSide(trade.entrySide);
        const subTag = buildAlpacaSubTag({
            profileId: trade.profileId,
            tradeId: trade.tradeId,
            intent: 'EXIT'
        }) || undefined;
        const fillPrice = trade.entryAvgPrice > EPSILON ? trade.entryAvgPrice : 0;

        const order: ReconciliationBackfillOrderInsert = {
            user_id: trade.userId,
            profile_id: trade.profileId,
            order_id: orderId,
            symbol: trade.symbol,
            type: 'market',
            side,
            qty: Number(trade.openQty.toFixed(8)),
            quantity: Number(trade.openQty.toFixed(8)),
            price: Number(fillPrice.toFixed(8)),
            status: 'filled',
            timestamp: nowTs,
            filled_at: nowIso,
            trade_id: trade.tradeId,
            action: 'EXIT',
            source: 'BOT',
            sub_tag: subTag
        };
        candidateOrders.push(order);

        preAuditRows.push({
            batch_id: batchId,
            profile_id: trade.profileId,
            symbol: trade.symbol,
            trade_id: trade.tradeId,
            exchange_order_id: null,
            exchange_client_order_id: null,
            backfill_order_id: orderId,
            filled_qty: order.qty,
            filled_price: order.price,
            filled_at: order.filled_at || null,
            dry_run: !options.apply,
            decision: options.apply ? 'MANUAL_OVERRIDE_PENDING' : 'MANUAL_OVERRIDE_DRY',
            reason: 'manual_override_user_approved_no_exchange_evidence',
            metadata: {
                openQtyBefore: trade.openQty,
                entryQty: trade.entryQty,
                exitQty: trade.exitQty,
                fillPriceBasis: 'entry_weighted_avg_price'
            }
        });
    }

    if (preAuditRows.length > 0) {
        const preSaved = await supabaseService.insertReconciliationBackfillAuditRows(preAuditRows);
        if (!preSaved) {
            throw new Error('Failed to save manual override pre-audit rows.');
        }
    }

    let insertedRows = 0;
    if (options.apply && candidateOrders.length > 0) {
        const orderIds = candidateOrders.map((row) => row.order_id);
        const existingBefore = await supabaseService.getExistingOrderIds(orderIds);
        const ok = await supabaseService.upsertReconciliationBackfillOrders(candidateOrders);
        if (!ok) {
            throw new Error('Failed to apply manual override rows.');
        }
        const existingAfter = await supabaseService.getExistingOrderIds(orderIds);
        insertedRows = candidateOrders.filter((row) => !existingBefore.has(row.order_id) && existingAfter.has(row.order_id)).length;

        const postAuditRows: ReconciliationBackfillAuditInsert[] = candidateOrders.map((row) => ({
            batch_id: batchId,
            profile_id: row.profile_id,
            symbol: row.symbol,
            trade_id: row.trade_id,
            exchange_order_id: null,
            exchange_client_order_id: null,
            backfill_order_id: row.order_id,
            filled_qty: row.qty,
            filled_price: row.price,
            filled_at: row.filled_at || null,
            dry_run: false,
            decision: existingBefore.has(row.order_id) ? 'MANUAL_OVERRIDE_SKIP_EXISTING' : 'MANUAL_OVERRIDE_APPLIED',
            reason: existingBefore.has(row.order_id) ? 'already_exists' : 'manual_override_inserted',
            metadata: {
                matchedBy: 'manual_override'
            },
            applied_at: !existingBefore.has(row.order_id) ? new Date().toISOString() : null
        }));
        const postSaved = await supabaseService.insertReconciliationBackfillAuditRows(postAuditRows);
        if (!postSaved) {
            throw new Error('Failed to save manual override post-audit rows.');
        }
    }

    console.log(JSON.stringify({
        mode: options.apply ? 'apply' : 'dry-run',
        batchId,
        requestedTrades: options.tradeIds,
        proposedRows: candidateOrders.length,
        insertedRows,
        skipped
    }, null, 2));
};

run().catch((error) => {
    const message = error instanceof Error ? error.message : String(error);
    console.error(JSON.stringify({ error: message }, null, 2));
    process.exit(1);
});
