import 'dotenv/config';
import Alpaca from '@alpacahq/alpaca-trade-api';
import { createClient } from '@supabase/supabase-js';

type OrderRow = {
    id?: string;
    order_id?: string | null;
    symbol?: string | null;
    status?: string | null;
    qty?: number | string | null;
    price?: number | string | null;
    source?: string | null;
    created_at?: string | null;
    updated_at?: string | null;
    filled_at?: string | null;
};

const args = process.argv.slice(2);
const applyMode = args.includes('--apply');
const lookbackArg = args.find((arg) => arg.startsWith('--lookback-hours='));
const LOOKBACK_HOURS = Number((lookbackArg ? lookbackArg.split('=')[1] : '') || '48');
const PAGE_SIZE = 1000;
const SYNTHETIC_PREFIXES = ['BFILL-', 'MANOVR-', 'RECON-BF', 'RECON-', 'SYNC-'];

const supabaseUrl = String(process.env.SUPABASE_URL || '').trim();
const supabaseKey = String(
    process.env.SUPABASE_KEY
    || process.env.SUPABASE_SERVICE_ROLE_KEY
    || process.env.SUPABASE_ANON_KEY
    || ''
).trim();
const alpacaApiKey = String(process.env.ALPACA_API_KEY || '').trim();
const alpacaApiSecret = String(process.env.ALPACA_API_SECRET || '').trim();
const paperTrading = String(process.env.PAPER_TRADING || 'true').toLowerCase() === 'true';

if (!supabaseUrl || !supabaseKey) {
    throw new Error('Missing Supabase credentials.');
}
if (!alpacaApiKey || !alpacaApiSecret) {
    throw new Error('Missing Alpaca credentials.');
}
if (!Number.isFinite(LOOKBACK_HOURS) || LOOKBACK_HOURS <= 0) {
    throw new Error(`Invalid --lookback-hours value: ${lookbackArg}`);
}

const supabase = createClient(supabaseUrl, supabaseKey);
const alpaca = new (Alpaca as any)({
    keyId: alpacaApiKey,
    secretKey: alpacaApiSecret,
    paper: paperTrading
});

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

const normalizeStatus = (value: unknown): string => {
    const normalized = String(value || '').trim().toLowerCase();
    if (normalized === 'partially-filled') return 'partially_filled';
    return normalized;
};

const isSyntheticOrderId = (orderId: string): boolean => {
    const normalized = String(orderId || '').trim().toUpperCase();
    if (!normalized) return false;
    return SYNTHETIC_PREFIXES.some((prefix) => normalized.startsWith(prefix));
};

const pctDiff = (left: number, right: number): number => {
    const denom = Math.max(Math.abs(left), Math.abs(right), 1e-9);
    return Math.abs(left - right) / denom;
};

const run = async () => {
    const sinceIso = new Date(Date.now() - LOOKBACK_HOURS * 60 * 60 * 1000).toISOString();
    const rows: OrderRow[] = [];
    let offset = 0;

    for (;;) {
        const { data, error } = await supabase
            .from('orders')
            .select('id,order_id,symbol,status,qty,price,source,created_at,updated_at,filled_at')
            .in('status', ['filled', 'partially_filled', 'partially-filled'])
            .gte('created_at', sinceIso)
            .order('created_at', { ascending: false })
            .range(offset, offset + PAGE_SIZE - 1);

        if (error) throw error;
        const chunk = (data || []) as OrderRow[];
        if (!chunk.length) break;
        rows.push(...chunk);
        if (chunk.length < PAGE_SIZE) break;
        offset += PAGE_SIZE;
    }

    const candidates = rows.filter((row) => {
        const orderId = String(row.order_id || '').trim();
        if (!orderId) return false;
        if (isSyntheticOrderId(orderId)) return false;
        const source = String(row.source || '').trim().toUpperCase();
        if (source === 'MANUAL') return false;
        return true;
    });

    const summary = {
        mode: applyMode ? 'apply' : 'dry-run',
        lookbackHours: LOOKBACK_HOURS,
        sinceIso,
        totalRows: rows.length,
        candidates: candidates.length,
        checked: 0,
        exchangeMissing: 0,
        noFillOnExchange: 0,
        driftDetected: 0,
        updated: 0,
        samples: [] as Array<Record<string, unknown>>
    };

    for (const row of candidates) {
        const orderId = String(row.order_id || '').trim();
        summary.checked += 1;

        let exchangeOrder: any;
        try {
            exchangeOrder = await (alpaca as any).getOrder(orderId);
        } catch {
            summary.exchangeMissing += 1;
            continue;
        }
        if (!exchangeOrder) {
            summary.exchangeMissing += 1;
            continue;
        }

        const exchangeStatus = normalizeStatus(exchangeOrder.status);
        const exchangeQty = toNumber(exchangeOrder.filled_qty ?? exchangeOrder.filledQty ?? exchangeOrder.filled_quantity);
        const exchangePrice = toNumber(exchangeOrder.filled_avg_price);
        if (!(exchangeQty > 0) || !(exchangePrice > 0)) {
            summary.noFillOnExchange += 1;
            continue;
        }

        const dbStatus = normalizeStatus(row.status);
        const dbQty = toNumber(row.qty);
        const dbPrice = toNumber(row.price);
        const qtyDrift = dbQty <= 0 || pctDiff(exchangeQty, dbQty) > 1e-6;
        const priceDrift = dbPrice <= 0 || pctDiff(exchangePrice, dbPrice) > 5e-4;
        const statusDrift = exchangeStatus && exchangeStatus !== dbStatus;
        if (!qtyDrift && !priceDrift && !statusDrift) continue;

        summary.driftDetected += 1;
        if (summary.samples.length < 20) {
            summary.samples.push({
                order_id: orderId,
                symbol: row.symbol,
                db_status: row.status,
                ex_status: exchangeOrder.status,
                db_qty: dbQty,
                ex_qty: exchangeQty,
                db_price: dbPrice,
                ex_price: exchangePrice
            });
        }

        if (!applyMode) continue;
        const filledAtRaw = String(exchangeOrder.filled_at || '').trim();
        const parsedFilledAt = filledAtRaw ? Date.parse(filledAtRaw) : NaN;
        const filledAtIso = Number.isFinite(parsedFilledAt) && parsedFilledAt > 0
            ? new Date(parsedFilledAt).toISOString()
            : new Date().toISOString();

        const { error: updateError } = await supabase
            .from('orders')
            .update({
                status: exchangeStatus || 'filled',
                qty: exchangeQty,
                price: exchangePrice,
                filled_at: filledAtIso,
                updated_at: new Date().toISOString()
            })
            .eq('order_id', orderId);
        if (updateError) throw updateError;
        summary.updated += 1;
    }

    console.log(JSON.stringify(summary, null, 2));
};

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

