import assert from 'node:assert/strict';
import { supabaseService } from '../src/services/SupabaseService.js';

type Row = Record<string, any>;
type Filter =
    | { op: 'eq'; field: string; value: any }
    | { op: 'in'; field: string; value: any[] }
    | { op: 'lt'; field: string; value: any };

class FakeSupabaseClient {
    public orders: Row[];
    private rowId = 0;

    constructor(seedOrders: Row[] = []) {
        this.orders = seedOrders.map((row) => ({ ...row }));
        for (const row of this.orders) {
            this.rowId = Math.max(this.rowId, Number(String(row.id || '').replace(/[^0-9]/g, '')) || 0);
        }
    }

    public nextId(): string {
        this.rowId += 1;
        return `row-${this.rowId}`;
    }

    from(table: string) {
        if (table !== 'orders') {
            throw new Error(`Fake client only supports orders table (received: ${table})`);
        }
        return new FakeOrdersQuery(this);
    }
}

class FakeOrdersQuery implements PromiseLike<{ data: any; error: any }> {
    private mode: 'select' | 'update' = 'select';
    private filters: Filter[] = [];
    private sortField: string | null = null;
    private sortAscending = true;
    private limitCount: number | null = null;
    private updatePayload: Row = {};

    constructor(private readonly db: FakeSupabaseClient) { }

    select(_columns: string) {
        this.mode = 'select';
        return this;
    }

    eq(field: string, value: any) {
        this.filters.push({ op: 'eq', field, value });
        return this;
    }

    in(field: string, value: any[]) {
        this.filters.push({ op: 'in', field, value });
        return this;
    }

    lt(field: string, value: any) {
        this.filters.push({ op: 'lt', field, value });
        return this;
    }

    order(field: string, options?: { ascending?: boolean }) {
        this.sortField = field;
        this.sortAscending = options?.ascending !== false;
        return this;
    }

    limit(count: number) {
        this.limitCount = count;
        return this;
    }

    update(payload: Row) {
        this.mode = 'update';
        this.updatePayload = { ...payload };
        return this;
    }

    insert(rows: Row[]) {
        const inserted = rows.map((row) => {
            const id = row.id || this.db.nextId();
            const createdAt = row.created_at || new Date().toISOString();
            return { ...row, id, created_at: createdAt };
        });
        this.db.orders.push(...inserted);
        return Promise.resolve({ data: inserted, error: null });
    }

    private matchesFilters(row: Row): boolean {
        return this.filters.every((filter) => {
            const value = row[filter.field];
            if (filter.op === 'eq') {
                return filter.value === null ? value == null : value === filter.value;
            }
            if (filter.op === 'in') {
                return filter.value.includes(value);
            }

            const leftRaw = value;
            const rightRaw = filter.value;
            const leftTs = Date.parse(String(leftRaw || ''));
            const rightTs = Date.parse(String(rightRaw || ''));
            if (Number.isFinite(leftTs) && Number.isFinite(rightTs)) {
                return leftTs < rightTs;
            }
            return String(leftRaw || '') < String(rightRaw || '');
        });
    }

    private executeSelect(): { data: any; error: any } {
        let rows = this.db.orders.filter((row) => this.matchesFilters(row)).map((row) => ({ ...row }));
        if (this.sortField) {
            const field = this.sortField;
            const direction = this.sortAscending ? 1 : -1;
            rows = rows.sort((a, b) => {
                const aValue = a[field];
                const bValue = b[field];
                const aTs = Date.parse(String(aValue || ''));
                const bTs = Date.parse(String(bValue || ''));
                if (Number.isFinite(aTs) && Number.isFinite(bTs) && aTs !== bTs) {
                    return direction * (aTs - bTs);
                }
                if (aValue === bValue) return 0;
                return direction * (String(aValue || '').localeCompare(String(bValue || '')));
            });
        }
        if (this.limitCount !== null) {
            rows = rows.slice(0, this.limitCount);
        }
        return { data: rows, error: null };
    }

    private executeUpdate(): { data: any; error: any } {
        const updatedRows: Row[] = [];
        for (const row of this.db.orders) {
            if (!this.matchesFilters(row)) continue;
            Object.assign(row, this.updatePayload);
            updatedRows.push({ ...row });
        }
        return { data: updatedRows, error: null };
    }

    then<TResult1 = { data: any; error: any }, TResult2 = never>(
        onfulfilled?: ((value: { data: any; error: any }) => TResult1 | PromiseLike<TResult1>) | null,
        onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
    ): Promise<TResult1 | TResult2> {
        try {
            const result = this.mode === 'update' ? this.executeUpdate() : this.executeSelect();
            if (!onfulfilled) {
                return Promise.resolve(result as TResult1);
            }
            return Promise.resolve(onfulfilled(result));
        } catch (error) {
            if (onrejected) {
                return Promise.resolve(onrejected(error));
            }
            return Promise.reject(error);
        }
    }
}

async function run() {
    const originalClient = (supabaseService as any).client;
    const fakeClient = new FakeSupabaseClient();
    (supabaseService as any).client = fakeClient as any;

    try {
        const now = Date.now();
        await supabaseService.logOrder({
            user_id: 'u-1',
            profile_id: 'p-1',
            order_id: 'ORD-1',
            symbol: 'BTC/USD',
            type: 'market',
            side: 'BUY',
            qty: 1,
            price: 100,
            status: 'pending_new',
            timestamp: now,
            trade_id: 'TRD-1',
            action: 'ENTRY',
            sub_tag: 'BL:PAPER:PTEST:TTEST:ENTRY'
        });
        assert.equal(fakeClient.orders.length, 1, 'Expected first order insert to create one row.');

        await supabaseService.logOrder({
            user_id: 'u-1',
            profile_id: 'p-1',
            order_id: 'ORD-1',
            symbol: 'BTC/USD',
            type: 'market',
            side: 'BUY',
            qty: 1,
            price: 100,
            status: 'pending_new',
            timestamp: now + 1000,
            trade_id: 'TRD-1',
            action: 'ENTRY'
        });
        assert.equal(fakeClient.orders.length, 1, 'Duplicate order_id within same profile must be upserted, not inserted.');

        await supabaseService.updateOrderStatus('ORD-1', 'filled', new Date(now + 2000), 101, 1.5);
        await supabaseService.logOrder({
            user_id: 'u-1',
            profile_id: 'p-1',
            order_id: 'ORD-1',
            symbol: 'BTC/USD',
            type: 'market',
            side: 'BUY',
            qty: 1,
            price: 99,
            status: 'pending_new',
            timestamp: now + 3000,
            trade_id: 'TRD-1',
            action: 'ENTRY'
        });

        const persisted = fakeClient.orders.find((row) => row.order_id === 'ORD-1' && row.profile_id === 'p-1');
        assert(persisted, 'Expected persisted order row to exist after updates.');
        assert.equal(persisted.status, 'filled', 'Order status must not downgrade from terminal status on duplicate log.');
        assert.equal(Number(persisted.qty), 1.5, 'Order qty must retain terminal filled quantity.');
        assert.equal(Number(persisted.price), 101, 'Order price must retain terminal fill price.');
        assert.equal(persisted.sub_tag, 'BL:PAPER:PTEST:TTEST:ENTRY', 'Order sub_tag must persist for traceability.');

        await supabaseService.logOrder({
            user_id: 'u-1',
            profile_id: 'p-2',
            order_id: 'ORD-SHARED',
            symbol: 'ETH/USD',
            type: 'market',
            side: 'BUY',
            qty: 1,
            price: 2000,
            status: 'pending_new',
            timestamp: now,
            trade_id: 'TRD-SHARED-1',
            action: 'ENTRY'
        });
        await supabaseService.logOrder({
            user_id: 'u-1',
            profile_id: 'p-3',
            order_id: 'ORD-SHARED',
            symbol: 'ETH/USD',
            type: 'market',
            side: 'BUY',
            qty: 1,
            price: 2000,
            status: 'pending_new',
            timestamp: now + 10,
            trade_id: 'TRD-SHARED-2',
            action: 'ENTRY'
        });
        const sharedRows = fakeClient.orders.filter((row) => row.order_id === 'ORD-SHARED');
        assert.equal(sharedRows.length, 2, 'Same exchange order id across different profiles must remain isolated.');

        const staleCreatedAt = new Date(Date.now() - 10 * 60_000).toISOString();
        fakeClient.orders.push(
            { id: fakeClient.nextId(), order_id: 'ORD-PN', status: 'pending_new', created_at: staleCreatedAt, symbol: 'BTC/USD' },
            { id: fakeClient.nextId(), order_id: 'ORD-P', status: 'pending', created_at: staleCreatedAt, symbol: 'BTC/USD' },
            { id: fakeClient.nextId(), order_id: 'ORD-A', status: 'accepted', created_at: staleCreatedAt, symbol: 'BTC/USD' },
            { id: fakeClient.nextId(), order_id: 'ORD-N', status: 'new', created_at: staleCreatedAt, symbol: 'BTC/USD' },
            { id: fakeClient.nextId(), order_id: 'ORD-F', status: 'filled', created_at: staleCreatedAt, symbol: 'BTC/USD' },
            { id: fakeClient.nextId(), order_id: 'ORD-YOUNG', status: 'pending_new', created_at: new Date().toISOString(), symbol: 'BTC/USD' }
        );

        const stale = await supabaseService.getStaleOrders(5);
        const staleIds = new Set((stale || []).map((row: any) => String(row.order_id || row.id)));
        assert(staleIds.has('ORD-PN'), 'Expected pending_new to be considered stale.');
        assert(staleIds.has('ORD-P'), 'Expected pending to be considered stale.');
        assert(staleIds.has('ORD-A'), 'Expected accepted to be considered stale.');
        assert(staleIds.has('ORD-N'), 'Expected new to be considered stale.');
        assert(!staleIds.has('ORD-F'), 'Filled orders must not be included in stale-pending query.');
        assert(!staleIds.has('ORD-YOUNG'), 'Recent pending_new orders must not be included as stale.');

        console.log('[supabase-order-persistence-regressions] OK: order upsert and stale status selection are stable');
    } finally {
        (supabaseService as any).client = originalClient;
    }
}

await run();
