import TradingView from "@mathieuc/tradingview";
import { logInfo, logDebug, logWarn, logError } from "../utils/logger";
import { sendCandlesToServer } from "../api/serverApi";

export interface TVClientOptions {
    sessionId: string;
    signature: string;
    symbol: string;
    timeframe: string;
    apiUrl: string;
    batchSize: number;
    minDateMs: number;
    maxDateMs: number;
    perQuery: number;
}

export class TVClient {
    private client: any;
    private options: TVClientOptions;
    private pendingCandles: any[] = [];
    private global_last_time_sec: number;
    private requestCounter = 0;
    private initialLoadComplete = false;
    private replayResetLock: string | null = null;

    constructor(options: TVClientOptions) {
        this.options = options;
        this.global_last_time_sec = options.maxDateMs / 1_000;
        this.client = new TradingView.Client({
            token: options.sessionId,
            signature: options.signature,
        });
        logInfo("TradingView client initialized");
    }

    async start() {
        let requestCounter = 0;
        let global_last_time_sec = this.options.maxDateMs / 1_000;
        const pendingCandles: any[] = [];
        let initialLoadComplete = false;
        let emptySeriesCompletedCount = 0;
        let consecutiveNoUpdateCount = 0;
        let lastReplayResetRequestId: string | null = null;
        const { symbol, timeframe, apiUrl, batchSize, minDateMs, perQuery } = this.options;
        const client = this.client;

        logInfo("Starting data retrieval process");
        const chart = new client.Session.Chart();
        logDebug("Chart session created", {
            chartSessionID: chart.chartSessionID,
            replaySessionID: chart.replaySessionID,
        });
        const chartSessionId = chart.chartSessionID;
        const replaySessionId = chart.replaySessionID;
        const seriesId = `sds_${++requestCounter}`;
        const initialSeriesVersionId = `s${++requestCounter}`;
        const modifySeriesVersionId = `s${++requestCounter}`;
        const normalSymbolId = `sds_sym_${++requestCounter}`;
        const replaySymbolId = `sds_sym_${++requestCounter}`;
        const replayAddSeriesRequestId = `r_add_${++requestCounter}`;
        const replayResetRequestId = `r_reset_${++requestCounter}`;

        const terminateScript = async (reason: string) => {
            logWarn(`Terminating script: ${reason}`);
            if (pendingCandles.length > 0) {
                logInfo(`Sending remaining ${pendingCandles.length} candles to server before exit`);
                await sendCandlesToServer(apiUrl, symbol, timeframe, pendingCandles);
                pendingCandles.length = 0;
            }
            try {
                logDebug("Attempting to delete replay session before exit");
                await chart.client.send("replay_delete_session", [replaySessionId]);
            } catch (delError) {
                logWarn("Could not delete replay session during termination", delError);
            }
            logInfo("Closing client connection");
            client.end();
            return process.exit(0);
        };

        chart.on("replay_ok", (sessionId: string, requestId: string, ...args: any[]) => {
            if (!initialLoadComplete && requestId == replayResetRequestId) {
                console.log("Replay session started successfully", requestId);
                initialLoadComplete = true;
            }
            if (this.replayResetLock && requestId === this.replayResetLock) {
                logDebug("Unlocking replay_reset (replay_ok)", { lock: this.replayResetLock });
                this.replayResetLock = null;
            }
            consecutiveNoUpdateCount = 0;
        });

        chart.onUpdate(async (data: any) => {
            const seriesData = data[1]?.[seriesId]?.s;
            if (!seriesData || seriesData.length === 0) {
                logDebug("No relevant series data in update", data);
                emptySeriesCompletedCount++;
                logWarn(`series_completed event is empty. Count: ${emptySeriesCompletedCount}`);
                if (emptySeriesCompletedCount >= 5) {
                    logError("Received 5 empty series_completed events in a row. Stopping client.");
                    await terminateScript("Received 5 empty series_completed events in a row.");
                    return process.exit(1);
                }
                return;
            }
            emptySeriesCompletedCount = 0;
            consecutiveNoUpdateCount = 0;
            const newCandles = seriesData.map((r: any) => {
                const [timestamp, open, high, low, close, volume] = r.v;
                return { timestamp, open, high, low, close, volume };
            });
            let earliestTimeInBatch = global_last_time_sec;
            newCandles.forEach((candle: any) => {
                if (!pendingCandles.some(c => c.timestamp === candle.timestamp)) {
                    pendingCandles.push(candle);
                    if (candle.timestamp < earliestTimeInBatch) {
                        earliestTimeInBatch = candle.timestamp;
                    }
                }
            });
            if (earliestTimeInBatch < global_last_time_sec) {
                global_last_time_sec = earliestTimeInBatch;
            }
            logInfo(
                `Received ${newCandles.length} candles. Total unique: ${pendingCandles.length}. Earliest: ${new Date(
                    global_last_time_sec * 1000
                ).toISOString()}`
            );
            logDebug("Sample received candles:", newCandles.slice(0, 2));
            if (!initialLoadComplete) {
                initialLoadComplete = true;
                logDebug("Initial data load complete.");
                FetchMore("initial_load_complete", {});
            }
            if (pendingCandles.length >= batchSize) {
                const batch = pendingCandles.splice(0, batchSize);
                sendCandlesToServer(apiUrl, symbol, timeframe, batch);
            }
            // Unlock replay_reset if lock is set and update is received
            if (this.replayResetLock) {
                logDebug("Unlocking replay_reset (onUpdate)", { lock: this.replayResetLock });
                this.replayResetLock = null;
            }
        });

        chart.onError((...error: any) => {
            logError("Chart error occurred", error);
            logInfo("Terminating client due to error");
            terminateScript("Chart error occurred");
        });


        const FetchMore = async (eventName: string, data: any) => {
            logDebug(`Event received: \"${eventName}\"`, data);
            if (!initialLoadComplete) {
                logDebug(`Skipping FetchMore: initialLoadComplete=${initialLoadComplete}`);
                return;
            }
            if (global_last_time_sec * 1000 <= minDateMs) {
                logInfo(`Reached target start date: ${new Date(global_last_time_sec * 1000).toISOString()}`);
                logInfo("Stopping data retrieval.");
                if (pendingCandles.length > 0) {
                    logInfo(`Sending remaining ${pendingCandles.length} candles to server before exit`);
                    await sendCandlesToServer(apiUrl, symbol, timeframe, pendingCandles);
                    pendingCandles.length = 0;
                }
                logDebug("Deleting replay session");
                chart.client.send("replay_delete_session", [replaySessionId]);
                logInfo("Terminating client");
                return await terminateScript("Reached target start date");
            }
            logInfo(`Requesting ${perQuery} more candles (batch ${++requestCounter})...`);
            consecutiveNoUpdateCount++;
            if (consecutiveNoUpdateCount >= 5) {
                return await terminateScript("Sent 5 request_more_data without receiving any onUpdate or replay_ok.");
            }
            chart.client.send("request_more_data", [
                chartSessionId,
                seriesId,
                perQuery,
            ]).then(() => {
                logDebug("request_more_data sent successfully.");
            }).catch((error: any) => {
                logError("Failed to send request_more_data", error);
            });
        };

        chart.on("series_completed", async (args: any[]) => {
            try {
                logDebug(`series_completed event received`, { args });
                const eventChartSessionId = args[0];
                const eventSeriesId = args[1];
                const eventType = args[2];
                const eventVersionId = args[3];
                const eventDetails = args[4];
                // Ensure it's for our chart, series, and initial load is done
                if (eventChartSessionId !== chartSessionId) {
                    logDebug("Ignoring series_completed event: wrong session", {
                        expected: chartSessionId,
                        received: eventChartSessionId
                    });
                    return;
                }
                if (eventSeriesId !== seriesId) {
                    logDebug("Ignoring series_completed event: wrong series", {
                        expected: seriesId,
                        received: eventSeriesId
                    });
                    return;
                }
                if (!initialLoadComplete) {
                    logDebug("Ignoring series_completed event: initial load not complete");
                    return;
                }
                // if (eventType === 'replay' && eventDetails?.data_completed === 'end') {
                //     logInfo("Replay session completed successfully. No more data available.");
                //     if (pendingCandles.length > 0) {
                //         logInfo(`Sending remaining ${pendingCandles.length} candles to server before exit`);
                //         await sendCandlesToServer(apiUrl, symbol, timeframe, pendingCandles);
                //         pendingCandles.length = 0;
                //     }
                //     logDebug("Deleting replay session");
                //     chart.client.send("replay_delete_session", [replaySessionId]);
                //     logInfo("Terminating client");
                //     client.end();
                //     return process.exit(0);
                // }
                if (eventType === 'replay' && eventDetails?.data_completed === 'limit') {
                    if (this.replayResetLock) {
                        logDebug("replay_reset already in progress, skipping", { lock: this.replayResetLock });
                        return;
                    }
                    const replayResetRequestId = `r_reset_${++requestCounter}`;
                    this.replayResetLock = replayResetRequestId;
                    logDebug(`Resetting replay point to ${new Date(global_last_time_sec * 1000).toISOString()} (Request ID: ${replayResetRequestId})`);
                    chart.client.send("replay_reset", [
                        replaySessionId,
                        replayResetRequestId,
                        global_last_time_sec,
                    ]).catch(async (error: any) => {
                        logError("Failed to reset replay session after hitting limit", error);
                        this.replayResetLock = null;
                        await terminateScript("Failed to reset replay session after limit hit.");
                    });
                } else {
                    FetchMore("series_completed", { status: eventVersionId });
                }
            } catch (error) {
                logError("Error in series_completed event handler");
                logWarn("Continuing execution despite error in event handler");
            }
        });

        chart.on("timescale_update", (data: any) => {
            logDebug("timescale_update event", data);
            if (data?.[seriesId]?.s && initialLoadComplete) {
                FetchMore("timescale_update", {});
            }
        });

        try {
            logInfo("Setting up chart and replay...");
            logDebug("1. Setting timezone to UTC");
            await chart.client.send("switch_timezone", [chartSessionId, "Etc/UTC"]);
            const normalSymbolInit = {
                symbol: symbol,
                adjustment: "splits",
                session: "regular",
            };
            logDebug("2. Resolving normal symbol", normalSymbolInit);
            await chart.client.send("resolve_symbol", [
                chartSessionId,
                normalSymbolId,
                `=${JSON.stringify(normalSymbolInit)}`,
            ]);
            logDebug(`Normal symbol resolved with ID: ${normalSymbolId}`);
            logDebug(`3. Creating series with ID: ${seriesId} using normal symbol ID: ${normalSymbolId}`);
            await chart.client.send("create_series", [
                chartSessionId,
                seriesId,
                initialSeriesVersionId,
                normalSymbolId,
                timeframe,
                10,
                ""
            ]);
            logDebug(`Series created with ID: ${seriesId}`);
            logDebug(`4. Creating replay session with ID: ${replaySessionId}`);
            await chart.client.send("replay_create_session", [replaySessionId]);
            logDebug("Replay session created");
            logDebug(`5. Adding series to replay session (Request ID: ${replayAddSeriesRequestId})`, normalSymbolInit);
            await chart.client.send("replay_add_series", [
                replaySessionId,
                replayAddSeriesRequestId,
                `=${JSON.stringify(normalSymbolInit)}`,
                timeframe,
            ]);
            logDebug("Series added to replay session");
            logDebug(`6. Resetting replay point to ${new Date(global_last_time_sec * 1000).toISOString()} (Request ID: ${replayResetRequestId})`);
            await chart.client.send("replay_reset", [
                replaySessionId,
                replayResetRequestId,
                global_last_time_sec,
            ]);
            logDebug("Replay point reset");
            const replaySymbolInit = {
                replay: replaySessionId,
                symbol: normalSymbolInit
            };
            logDebug("7. Resolving replay symbol", replaySymbolInit);
            await chart.client.send("resolve_symbol", [
                chartSessionId,
                replaySymbolId,
                `=${JSON.stringify(replaySymbolInit)}`,
            ]);
            logDebug(`Replay symbol resolved with ID: ${replaySymbolId}`);
            logDebug(`8. Modifying series ${seriesId} to use replay symbol ${replaySymbolId}`);
            await chart.client.send("modify_series", [
                chartSessionId,
                seriesId,
                modifySeriesVersionId,
                replaySymbolId,
                timeframe,
                ""
            ]);
            logDebug("Series modified to use replay symbol. Waiting for initial data...");
            logInfo("Chart and replay setup complete. Waiting for data...");
        } catch (error) {
            logError("Failed during chart/replay initialization", error);
            return await terminateScript("Failed during chart/replay initialization");
        }
        const safetyTimeout = setTimeout(async () => {
            logWarn("Safety timeout reached. Terminating process.");
            await terminateScript("Safety timeout reached.");
        }, 30 * 60 * 1000);
        chart.on('close', () => {
            logInfo("Client connection closed.");
            clearTimeout(safetyTimeout);
        });
        logInfo("Scraper setup complete and running. Fetching data backwards from now...");
    }
}
