import 'dotenv/config';
import { randomUUID } from 'crypto';
import { createClient } from '@supabase/supabase-js';
import { normalizeOrderAction, normalizeTradeSide } from '../src/domain/tradingEnums.js';

type CliOptions = {
    apply: boolean;
    tradeIds: string[];
    restoreBatchId?: string;
    allowCrossTrade: boolean;
    allowPartial: boolean;
};

type OrderRow = {
    id?: string;
    order_id?: string | null;
    user_id?: string | null;
    profile_id?: string | null;
    trade_id?: string | null;
    symbol?: string | null;
    action?: string | null;
    side?: string | null;
    qty?: number | string | null;
    quantity?: number | string | null;
    price?: number | string | null;
    status?: string | null;
    source?: string | null;
    created_at?: string | null;
    updated_at?: string | null;
};

type AuditRow = {
    id: number;
    batch_id?: string | null;
    profile_id?: string | null;
    symbol?: string | null;
    trade_id?: string | null;
    exchange_order_id?: string | null;
    backfill_order_id?: string | null;
    decision?: string | null;
    reason?: string | null;
    metadata?: Record<string, any> | null;
    created_at?: string | null;
    reverted_at?: string | null;
};

type CandidateRow = {
    order: OrderRow;
    qty: number;
    exchangeOrderId: string;
    realOrder: OrderRow | null;
    safe: boolean;
    unsafeReason?: string;
};

type ChosenSubset = {
    orderIds: string[];
    sumQty: number;
    nextOpenQty: number;
    withinDust: boolean;
};

type TradeEvaluation = {
    tradeId: string;
    profileId: string;
    symbol: string;
    entryQty: number;
    exitQty: number;
    openQtyBefore: number;
    openQtyAfter: number;
    dustThreshold: number;
    candidates: CandidateRow[];
    selectedOrderIds: string[];
    decision: 'SKIP' | 'DRY_RUN' | 'APPLIED' | 'NO_GO';
    reason: string;
};

const EPSILON = 1e-8;
const DUST_ABS_QTY = 0.001;
const DUST_REL_PCT = 0.002; // 0.2%
const FILLED_STATUSES = new Set(['filled', 'partially_filled', 'partially-filled']);
const APPLIED_DECISIONS = new Set([
    'APPLIED',
    'MANUAL_OVERRIDE_APPLIED',
    'SKIP_EXISTING',
    'MANUAL_OVERRIDE_SKIP_EXISTING',
    'ATTRIB_REPAIR_APPLIED'
]);
const STATUS_REVERT_TARGET = 'canceled';

const parseArgs = (argv: string[]): CliOptions => {
    const tradeIds = new Set<string>();
    let apply = false;
    let restoreBatchId: string | undefined;
    let allowCrossTrade = false;
    let allowPartial = false;

    for (const raw of argv) {
        const arg = String(raw || '').trim();
        if (!arg) continue;
        if (arg === '--apply') {
            apply = true;
            continue;
        }
        if (arg === '--allow-cross-trade') {
            allowCrossTrade = true;
            continue;
        }
        if (arg === '--allow-partial') {
            allowPartial = true;
            continue;
        }
        if (arg.startsWith('--restore-batch=')) {
            const value = String(arg.slice('--restore-batch='.length) || '').trim();
            if (value) restoreBatchId = value;
            continue;
        }
        if (arg.startsWith('--trade=')) {
            const value = String(arg.slice('--trade='.length) || '').trim();
            if (value) tradeIds.add(value);
            continue;
        }
    }

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

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

const normalizeStatus = (value: unknown): string => String(value || '').trim().toLowerCase();

const inferAction = (actionRaw?: string | null, sideRaw?: string | null): 'ENTRY' | 'EXIT' | undefined => {
    const explicit = normalizeOrderAction(actionRaw || undefined);
    if (explicit) return explicit;
    const side = normalizeTradeSide(sideRaw || 'BUY');
    return side === 'BUY' ? 'ENTRY' : 'EXIT';
};

const parseMetadata = (value: unknown): Record<string, any> => {
    if (!value) return {};
    if (typeof value === 'object') return value as Record<string, any>;
    if (typeof value === 'string') {
        try {
            const parsed = JSON.parse(value);
            if (parsed && typeof parsed === 'object') return parsed as Record<string, any>;
        } catch {
            return {};
        }
    }
    return {};
};

const pickBestSubset = (
    openQtyBefore: number,
    candidates: CandidateRow[],
    dustThreshold: number,
    allowPartial: boolean
): ChosenSubset | null => {
    if (candidates.length === 0) return null;
    if (!(openQtyBefore < -EPSILON)) return null;
    if (candidates.length > 20) return null;

    const n = candidates.length;
    const totalMasks = 1 << n;
    let best: ChosenSubset | null = null;

    for (let mask = 1; mask < totalMasks; mask += 1) {
        let sumQty = 0;
        const orderIds: string[] = [];
        for (let idx = 0; idx < n; idx += 1) {
            if ((mask & (1 << idx)) === 0) continue;
            const candidate = candidates[idx];
            sumQty += candidate.qty;
            orderIds.push(String(candidate.order.order_id || '').trim());
        }

        const nextOpenQty = Number((openQtyBefore + sumQty).toFixed(8));
        const improved = Math.abs(nextOpenQty) + EPSILON < Math.abs(openQtyBefore);
        if (!improved) continue;

        const withinDust = Math.abs(nextOpenQty) <= dustThreshold + EPSILON;
        if (!withinDust && !allowPartial) continue;
        // Never move from over-closed to materially under-closed in strict mode.
        if (!allowPartial && nextOpenQty > dustThreshold + EPSILON) continue;

        const candidateBest: ChosenSubset = {
            orderIds,
            sumQty: Number(sumQty.toFixed(8)),
            nextOpenQty,
            withinDust
        };
        if (!best) {
            best = candidateBest;
            continue;
        }

        const bestAbs = Math.abs(best.nextOpenQty);
        const candAbs = Math.abs(candidateBest.nextOpenQty);
        if (candAbs + EPSILON < bestAbs) {
            best = candidateBest;
            continue;
        }
        if (Math.abs(candAbs - bestAbs) <= EPSILON && candidateBest.orderIds.length < best.orderIds.length) {
            best = candidateBest;
        }
    }

    return best;
};

const buildTradeLedger = (rows: OrderRow[]): {
    entryQty: number;
    exitQty: number;
    openQty: number;
    profileId: string;
    symbol: string;
} => {
    let entryQty = 0;
    let exitQty = 0;
    let profileId = '';
    let symbol = '';

    for (const row of rows) {
        const status = normalizeStatus(row.status);
        if (!FILLED_STATUSES.has(status)) continue;

        const qty = toNumber(row.qty ?? row.quantity);
        if (!(qty > EPSILON)) continue;
        const action = inferAction(row.action, row.side);
        if (!action) continue;
        if (!profileId) profileId = String(row.profile_id || '').trim();
        if (!symbol) symbol = String(row.symbol || '').trim();

        if (action === 'ENTRY') entryQty += qty;
        if (action === 'EXIT') exitQty += qty;
    }

    return {
        entryQty: Number(entryQty.toFixed(8)),
        exitQty: Number(exitQty.toFixed(8)),
        openQty: Number((entryQty - exitQty).toFixed(8)),
        profileId,
        symbol
    };
};

const runRestoreMode = async (
    supabase: ReturnType<typeof createClient>,
    options: CliOptions
): Promise<void> => {
    const restoreBatchId = String(options.restoreBatchId || '').trim();
    if (!restoreBatchId) {
        throw new Error('Missing --restore-batch=<BATCH_ID>.');
    }

    const { data: rows, error } = await supabase
        .from('reconciliation_backfill_audit')
        .select('id,batch_id,profile_id,symbol,trade_id,backfill_order_id,decision,reason,metadata,created_at,reverted_at')
        .eq('batch_id', restoreBatchId)
        .in('decision', ['ATTRIB_REPAIR_APPLIED'])
        .order('created_at', { ascending: true });
    if (error) throw error;

    const restoreTargets = ((rows || []) as AuditRow[])
        .filter((row) => String(row.backfill_order_id || '').trim().length > 0);

    const batchId = `ATTRIB-RESTORE-${randomUUID()}`;
    const nowIso = new Date().toISOString();
    const updates = restoreTargets.map((row) => {
        const metadata = parseMetadata(row.metadata);
        const restoreStatus = normalizeStatus(metadata.prevStatus || 'filled') || 'filled';
        return {
            row,
            restoreStatus
        };
    });

    let restoredRows = 0;
    if (options.apply && updates.length > 0) {
        for (const target of updates) {
            const orderId = String(target.row.backfill_order_id || '').trim();
            const { error: updateError } = await supabase
                .from('orders')
                .update({
                    status: target.restoreStatus,
                    updated_at: nowIso
                })
                .eq('order_id', orderId);
            if (updateError) throw updateError;
            restoredRows += 1;
        }

        const { error: markRevertedError } = await supabase
            .from('reconciliation_backfill_audit')
            .update({ reverted_at: nowIso })
            .eq('batch_id', restoreBatchId)
            .in('decision', ['ATTRIB_REPAIR_APPLIED']);
        if (markRevertedError) throw markRevertedError;

        const restoreAuditRows = updates.map((target) => ({
            batch_id: batchId,
            profile_id: String(target.row.profile_id || '').trim(),
            symbol: String(target.row.symbol || '').trim(),
            trade_id: String(target.row.trade_id || '').trim(),
            exchange_order_id: null,
            exchange_client_order_id: null,
            backfill_order_id: String(target.row.backfill_order_id || '').trim(),
            filled_qty: null,
            filled_price: null,
            filled_at: null,
            dry_run: false,
            decision: 'ATTRIB_RESTORE_APPLIED',
            reason: `status_restored_from_${restoreBatchId}`,
            metadata: {
                restoredFromBatch: restoreBatchId,
                restoredFromAuditId: target.row.id,
                restoredToStatus: target.restoreStatus
            },
            applied_at: nowIso
        }));

        const { error: restoreAuditError } = await supabase
            .from('reconciliation_backfill_audit')
            .insert(restoreAuditRows);
        if (restoreAuditError) throw restoreAuditError;
    }

    console.log(JSON.stringify({
        mode: options.apply ? 'restore-apply' : 'restore-dry-run',
        restoreBatchId,
        batchId,
        targetRows: updates.length,
        restoredRows,
        targets: updates.map((target) => ({
            backfillOrderId: String(target.row.backfill_order_id || '').trim(),
            profileId: String(target.row.profile_id || '').trim(),
            tradeId: String(target.row.trade_id || '').trim(),
            restoreStatus: target.restoreStatus
        }))
    }, null, 2));
};

const runRepairMode = async (
    supabase: ReturnType<typeof createClient>,
    options: CliOptions
): Promise<void> => {
    if (options.tradeIds.length === 0) {
        throw new Error('Provide at least one --trade=<TRADE_ID> or use --restore-batch=<BATCH_ID>.');
    }

    const { data: tradeRows, error: tradeError } = await supabase
        .from('orders')
        .select('id,order_id,user_id,profile_id,trade_id,symbol,action,side,qty,quantity,price,status,source,created_at,updated_at')
        .in('trade_id', options.tradeIds)
        .order('created_at', { ascending: true });
    if (tradeError) throw tradeError;

    const rows = (tradeRows || []) as OrderRow[];
    const rowsByTrade = new Map<string, OrderRow[]>();
    for (const row of rows) {
        const tradeId = String(row.trade_id || '').trim();
        if (!tradeId) continue;
        const list = rowsByTrade.get(tradeId) || [];
        list.push(row);
        rowsByTrade.set(tradeId, list);
    }

    const candidateBfillOrderIds = Array.from(new Set(
        rows
            .map((row) => String(row.order_id || '').trim())
            .filter((orderId) => orderId.startsWith('BFILL-'))
    ));

    const preferredAuditByBackfill = new Map<string, AuditRow>();
    if (candidateBfillOrderIds.length > 0) {
        const { data: auditRows, error: auditError } = await supabase
            .from('reconciliation_backfill_audit')
            .select('id,batch_id,profile_id,symbol,trade_id,exchange_order_id,backfill_order_id,decision,reason,metadata,created_at,reverted_at')
            .in('backfill_order_id', candidateBfillOrderIds)
            .order('created_at', { ascending: false });
        if (auditError) throw auditError;

        const rowsByBackfill = new Map<string, AuditRow[]>();
        for (const row of (auditRows || []) as AuditRow[]) {
            const backfillOrderId = String(row.backfill_order_id || '').trim();
            if (!backfillOrderId) continue;
            const list = rowsByBackfill.get(backfillOrderId) || [];
            list.push(row);
            rowsByBackfill.set(backfillOrderId, list);
        }

        for (const [backfillOrderId, backfillAuditRows] of rowsByBackfill.entries()) {
            const preferred = backfillAuditRows.find((row) => {
                const decision = String(row.decision || '').trim();
                const exchangeOrderId = String(row.exchange_order_id || '').trim();
                return APPLIED_DECISIONS.has(decision) && exchangeOrderId.length > 0;
            }) || backfillAuditRows[0];
            if (preferred) {
                preferredAuditByBackfill.set(backfillOrderId, preferred);
            }
        }
    }

    const exchangeOrderIds = Array.from(new Set(
        Array.from(preferredAuditByBackfill.values())
            .map((row) => String(row.exchange_order_id || '').trim())
            .filter(Boolean)
    ));
    const realOrderByOrderId = new Map<string, OrderRow>();
    if (exchangeOrderIds.length > 0) {
        const { data: realRows, error: realError } = await supabase
            .from('orders')
            .select('id,order_id,user_id,profile_id,trade_id,symbol,action,side,qty,quantity,price,status,source,created_at,updated_at')
            .in('order_id', exchangeOrderIds);
        if (realError) throw realError;

        for (const row of (realRows || []) as OrderRow[]) {
            const orderId = String(row.order_id || '').trim();
            if (!orderId) continue;
            if (!realOrderByOrderId.has(orderId)) {
                realOrderByOrderId.set(orderId, row);
            }
        }
    }

    const batchId = `ATTRIB-REPAIR-${randomUUID()}`;
    const nowIso = new Date().toISOString();
    const evaluations: TradeEvaluation[] = [];
    const auditRowsToInsert: Record<string, any>[] = [];
    const orderIdsToRevert = new Set<string>();

    for (const tradeId of options.tradeIds) {
        const tradeRowsForId = rowsByTrade.get(tradeId) || [];
        if (tradeRowsForId.length === 0) {
            evaluations.push({
                tradeId,
                profileId: '',
                symbol: '',
                entryQty: 0,
                exitQty: 0,
                openQtyBefore: 0,
                openQtyAfter: 0,
                dustThreshold: DUST_ABS_QTY,
                candidates: [],
                selectedOrderIds: [],
                decision: 'NO_GO',
                reason: 'trade_not_found'
            });
            continue;
        }

        const ledger = buildTradeLedger(tradeRowsForId);
        const dustThreshold = Math.max(DUST_ABS_QTY, ledger.entryQty * DUST_REL_PCT);

        const candidates = tradeRowsForId
            .filter((row) => String(row.order_id || '').trim().startsWith('BFILL-'))
            .filter((row) => FILLED_STATUSES.has(normalizeStatus(row.status)))
            .filter((row) => inferAction(row.action, row.side) === 'EXIT')
            .map((row): CandidateRow => {
                const orderId = String(row.order_id || '').trim();
                const latestAudit = preferredAuditByBackfill.get(orderId);
                if (!latestAudit) {
                    return {
                        order: row,
                        qty: toNumber(row.qty ?? row.quantity),
                        exchangeOrderId: '',
                        realOrder: null,
                        safe: false,
                        unsafeReason: 'missing_audit_link'
                    };
                }

                if (!APPLIED_DECISIONS.has(String(latestAudit.decision || '').trim())) {
                    return {
                        order: row,
                        qty: toNumber(row.qty ?? row.quantity),
                        exchangeOrderId: String(latestAudit.exchange_order_id || '').trim(),
                        realOrder: null,
                        safe: false,
                        unsafeReason: `audit_not_applied:${String(latestAudit.decision || '').trim() || 'unknown'}`
                    };
                }

                const exchangeOrderId = String(latestAudit.exchange_order_id || '').trim();
                if (!exchangeOrderId) {
                    return {
                        order: row,
                        qty: toNumber(row.qty ?? row.quantity),
                        exchangeOrderId,
                        realOrder: null,
                        safe: false,
                        unsafeReason: 'missing_exchange_order_id'
                    };
                }

                const realOrder = realOrderByOrderId.get(exchangeOrderId) || null;
                if (!realOrder) {
                    return {
                        order: row,
                        qty: toNumber(row.qty ?? row.quantity),
                        exchangeOrderId,
                        realOrder: null,
                        safe: false,
                        unsafeReason: 'exchange_order_not_persisted_in_orders'
                    };
                }

                if (!FILLED_STATUSES.has(normalizeStatus(realOrder.status))) {
                    return {
                        order: row,
                        qty: toNumber(row.qty ?? row.quantity),
                        exchangeOrderId,
                        realOrder,
                        safe: false,
                        unsafeReason: `exchange_order_not_filled:${normalizeStatus(realOrder.status)}`
                    };
                }

                const realTradeId = String(realOrder.trade_id || '').trim();
                if (!options.allowCrossTrade && realTradeId !== tradeId) {
                    return {
                        order: row,
                        qty: toNumber(row.qty ?? row.quantity),
                        exchangeOrderId,
                        realOrder,
                        safe: false,
                        unsafeReason: `cross_trade_mapping:${realTradeId || 'null'}`
                    };
                }

                return {
                    order: row,
                    qty: toNumber(row.qty ?? row.quantity),
                    exchangeOrderId,
                    realOrder,
                    safe: true
                };
            });

        if (!(ledger.openQty < -EPSILON)) {
            evaluations.push({
                tradeId,
                profileId: ledger.profileId,
                symbol: ledger.symbol,
                entryQty: ledger.entryQty,
                exitQty: ledger.exitQty,
                openQtyBefore: ledger.openQty,
                openQtyAfter: ledger.openQty,
                dustThreshold: Number(dustThreshold.toFixed(8)),
                candidates,
                selectedOrderIds: [],
                decision: 'SKIP',
                reason: 'trade_not_overclosed'
            });
            continue;
        }

        const safeCandidates = candidates.filter((candidate) => candidate.safe && candidate.qty > EPSILON);
        if (safeCandidates.length === 0) {
            evaluations.push({
                tradeId,
                profileId: ledger.profileId,
                symbol: ledger.symbol,
                entryQty: ledger.entryQty,
                exitQty: ledger.exitQty,
                openQtyBefore: ledger.openQty,
                openQtyAfter: ledger.openQty,
                dustThreshold: Number(dustThreshold.toFixed(8)),
                candidates,
                selectedOrderIds: [],
                decision: 'NO_GO',
                reason: 'no_safe_candidates'
            });
            continue;
        }

        const bestSubset = pickBestSubset(ledger.openQty, safeCandidates, dustThreshold, options.allowPartial);
        if (!bestSubset) {
            evaluations.push({
                tradeId,
                profileId: ledger.profileId,
                symbol: ledger.symbol,
                entryQty: ledger.entryQty,
                exitQty: ledger.exitQty,
                openQtyBefore: ledger.openQty,
                openQtyAfter: ledger.openQty,
                dustThreshold: Number(dustThreshold.toFixed(8)),
                candidates,
                selectedOrderIds: [],
                decision: 'NO_GO',
                reason: options.allowPartial ? 'no_improving_subset' : 'no_subset_within_dust'
            });
            continue;
        }

        const resolvedDecision: TradeEvaluation['decision'] = options.apply ? 'APPLIED' : 'DRY_RUN';
        for (const orderId of bestSubset.orderIds) {
            orderIdsToRevert.add(orderId);
        }

        evaluations.push({
            tradeId,
            profileId: ledger.profileId,
            symbol: ledger.symbol,
            entryQty: ledger.entryQty,
            exitQty: ledger.exitQty,
            openQtyBefore: ledger.openQty,
            openQtyAfter: bestSubset.nextOpenQty,
            dustThreshold: Number(dustThreshold.toFixed(8)),
            candidates,
            selectedOrderIds: bestSubset.orderIds,
            decision: resolvedDecision,
            reason: bestSubset.withinDust ? 'synthetic_duplicate_subset_selected' : 'synthetic_duplicate_subset_selected_partial'
        });
    }

    if (options.apply && orderIdsToRevert.size > 0) {
        for (const orderId of orderIdsToRevert) {
            const { error: updateError } = await supabase
                .from('orders')
                .update({
                    status: STATUS_REVERT_TARGET,
                    updated_at: nowIso
                })
                .eq('order_id', orderId)
                .in('status', ['filled', 'partially_filled', 'partially-filled']);
            if (updateError) throw updateError;
        }
    }

    for (const evaluation of evaluations) {
        if (evaluation.selectedOrderIds.length > 0) {
            for (const orderId of evaluation.selectedOrderIds) {
                const candidate = evaluation.candidates.find((row) => String(row.order.order_id || '').trim() === orderId);
                if (!candidate) continue;
                const prevStatus = normalizeStatus(candidate.order.status);
                auditRowsToInsert.push({
                    batch_id: batchId,
                    profile_id: evaluation.profileId,
                    symbol: evaluation.symbol,
                    trade_id: evaluation.tradeId,
                    exchange_order_id: candidate.exchangeOrderId || null,
                    exchange_client_order_id: null,
                    backfill_order_id: orderId,
                    filled_qty: Number(candidate.qty.toFixed(8)),
                    filled_price: toNumber(candidate.order.price),
                    filled_at: null,
                    dry_run: !options.apply,
                    decision: options.apply ? 'ATTRIB_REPAIR_APPLIED' : 'ATTRIB_REPAIR_DRY',
                    reason: 'synthetic_duplicate_status_revert',
                    metadata: {
                        prevStatus: prevStatus || 'filled',
                        nextStatus: options.apply ? STATUS_REVERT_TARGET : `would_${STATUS_REVERT_TARGET}`,
                        openQtyBefore: evaluation.openQtyBefore,
                        openQtyAfter: evaluation.openQtyAfter,
                        dustThreshold: evaluation.dustThreshold,
                        realOrderTradeId: String(candidate.realOrder?.trade_id || '').trim() || null,
                        allowCrossTrade: options.allowCrossTrade,
                        allowPartial: options.allowPartial
                    },
                    applied_at: options.apply ? nowIso : null
                });
            }
        } else if (evaluation.decision === 'NO_GO') {
            auditRowsToInsert.push({
                batch_id: batchId,
                profile_id: evaluation.profileId || null,
                symbol: evaluation.symbol || null,
                trade_id: evaluation.tradeId,
                exchange_order_id: null,
                exchange_client_order_id: null,
                backfill_order_id: null,
                filled_qty: null,
                filled_price: null,
                filled_at: null,
                dry_run: !options.apply,
                decision: options.apply ? 'ATTRIB_REPAIR_NO_GO' : 'ATTRIB_REPAIR_NO_GO_DRY',
                reason: evaluation.reason,
                metadata: {
                    openQtyBefore: evaluation.openQtyBefore,
                    dustThreshold: evaluation.dustThreshold,
                    safeCandidates: evaluation.candidates.filter((row) => row.safe).length,
                    unsafeCandidates: evaluation.candidates.filter((row) => !row.safe).map((row) => ({
                        orderId: row.order.order_id,
                        reason: row.unsafeReason
                    }))
                },
                applied_at: null
            });
        }
    }

    if (auditRowsToInsert.length > 0) {
        const { error: auditInsertError } = await supabase
            .from('reconciliation_backfill_audit')
            .insert(auditRowsToInsert);
        if (auditInsertError) throw auditInsertError;
    }

    console.log(JSON.stringify({
        mode: options.apply ? 'apply' : 'dry-run',
        batchId,
        tradeIds: options.tradeIds,
        allowCrossTrade: options.allowCrossTrade,
        allowPartial: options.allowPartial,
        totalTrades: evaluations.length,
        appliedOrderStatusReverts: options.apply ? orderIdsToRevert.size : 0,
        evaluations: evaluations.map((evaluation) => ({
            tradeId: evaluation.tradeId,
            profileId: evaluation.profileId,
            symbol: evaluation.symbol,
            decision: evaluation.decision,
            reason: evaluation.reason,
            entryQty: evaluation.entryQty,
            exitQty: evaluation.exitQty,
            openQtyBefore: evaluation.openQtyBefore,
            openQtyAfter: evaluation.openQtyAfter,
            dustThreshold: evaluation.dustThreshold,
            selectedOrderIds: evaluation.selectedOrderIds,
            safeCandidateCount: evaluation.candidates.filter((row) => row.safe).length,
            unsafeCandidateCount: evaluation.candidates.filter((row) => !row.safe).length
        }))
    }, null, 2));
};

const run = async (): Promise<void> => {
    const options = parseArgs(process.argv.slice(2));
    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();
    if (!supabaseUrl || !supabaseKey) {
        throw new Error('Missing Supabase credentials. Expected SUPABASE_URL + SUPABASE_KEY/SUPABASE_SERVICE_ROLE_KEY.');
    }

    const supabase = createClient(supabaseUrl, supabaseKey);
    if (options.restoreBatchId) {
        await runRestoreMode(supabase, options);
        return;
    }
    await runRepairMode(supabase, options);
};

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