'use strict';
const os = require('os');
const ServerApiClient = require('./serverApiClient');
const { TaskType } = require('../contracts/taskTypes');

// Pausa después de completar una tarea antes de pedir la siguiente.
// No afecta al tiempo de espera cuando no hay tareas (eso lo gestiona el long-polling del servidor).
const POST_TASK_DELAY_MS = parseInt(process.env.WORKER_POST_TASK_DELAY_MS || '2000');
const HEARTBEAT_INTERVAL_MS = 20000;
// Timeout del fetch al servidor: debe ser mayor que LONG_POLL_MS del servidor (30s) + margen
const FETCH_TIMEOUT_MS = parseInt(process.env.WORKER_FETCH_TIMEOUT_MS || '45000');

class WorkerRuntime {
    constructor() {
        // ID estable: WORKER_ID del .env → hostname + primeros 8 chars del HWID (set por main-worker.js)
        // → hostname + hash estático de hostname. NUNCA Date.now() — cambiaría en cada reinicio.
        const _hwid = process.env.WORKER_HWID;
        this.workerId = process.env.WORKER_ID ||
            (_hwid
                ? `${os.hostname()}-${_hwid.slice(0, 8)}`
                : `${os.hostname()}-${require('crypto').createHash('md5').update(os.hostname()).digest('hex').slice(0, 8)}`
            );
        // Persistir para módulos que lean process.env.WORKER_ID directamente (scriptTaskHandler, etc.)
        if (!process.env.WORKER_ID) process.env.WORKER_ID = this.workerId;
        this.api = new ServerApiClient(process.env.TASK_SERVER_URL);
        this.running = false;
        this.currentTaskId = null;
        this.heartbeatTimer = null;
        this._taskCancelledByServer = false;
        this._activeCancelToken = null;

        // Registro lazy de handlers
        this._handlers = null;
    }

    get handlers() {
        if (!this._handlers) {
            // Worker es ejecutor puro: solo sabe ejecutar scripts de pasos.
            // Toda la lógica de Instagram vive en el servidor (step generators).
            this._handlers = {
                [TaskType.EXECUTE_SCRIPT]: new (require('./handlers/scriptTaskHandler'))(),
            };
        }
        return this._handlers;
    }

    log(msg) {
        console.log(`[WORKER ${this.workerId}] ${new Date().toISOString()} ${msg}`);
    }

    startHeartbeat(taskId) {
        this._taskCancelledByServer = false;
        this.heartbeatTimer = setInterval(async () => {
            try {
                const r = await this.api.heartbeat(taskId, this.workerId);
                if (r?.cancelled && !this._taskCancelledByServer) {
                    this.log(`Tarea ${taskId} cancelada por el servidor — se abortará al completar la operación actual`);
                    this._taskCancelledByServer = true;
                    if (this._activeCancelToken) this._activeCancelToken.cancelled = true;
                }
            } catch (_) {}
        }, HEARTBEAT_INTERVAL_MS);
    }

    stopHeartbeat() {
        if (this.heartbeatTimer) {
            clearInterval(this.heartbeatTimer);
            this.heartbeatTimer = null;
        }
    }

    async runOnce() {
        let res;
        try {
            res = await this.api.pullTask(this.workerId, FETCH_TIMEOUT_MS);
        } catch (err) {
            this.log(`Error contactando servidor: ${err.message}`);
            // Espera breve antes de reintentar si el servidor no está disponible
            await new Promise(r => setTimeout(r, 5000));
            return false;
        }

        const task = res?.task;
        if (!task) return false; // Long-poll expiró sin tarea

        this.currentTaskId = task.id;
        this._activeCancelToken = { cancelled: false };
        task._cancelToken = this._activeCancelToken;
        this.log(`Tarea recibida: ${task.id} (${task.type}) para @${task.account?.username}`);
        this.startHeartbeat(task.id);

        try {
            const handler = this.handlers[task.type];
            if (!handler) throw new Error(`Sin handler para tipo: ${task.type}`);

            const result = await handler.execute(task);

            if (this._taskCancelledByServer) {
                this.log(`Tarea ${task.id} completó ejecución pero fue cancelada — descartando resultado`);
            } else {
                await this.api.reportResult(task.id, this.workerId, {
                    status: 'DONE',
                    metrics: result || {},
                    finishedAt: new Date().toISOString(),
                });
                this.log(`Tarea completada: ${task.id}`);
            }
        } catch (err) {
            if (this._taskCancelledByServer) {
                this.log(`Tarea ${task.id} abortada por cancelación del servidor`);
            } else {
                this.log(`Tarea fallida: ${task.id} — ${err.message}`);
                await this.api.reportFailure(task.id, this.workerId, err).catch(() => {});
            }
        } finally {
            this.stopHeartbeat();
            this.currentTaskId = null;
            this._taskCancelledByServer = false;
            this._activeCancelToken = null;
        }
        return true; // indica que se ejecutó una tarea
    }

    async runLoop() {
        this.running = true;

        while (this.running) {
            try {
                await this.api.registerWorker(this.workerId, Object.values(TaskType), process.env.WORKER_HWID || null);
                break;
            } catch (err) {
                this.log(`Servidor no disponible: ${err.message}. Reintentando en 5s...`);
                await new Promise(r => setTimeout(r, 5000));
            }
        }
        if (!this.running) return;

        this.log('Worker registrado, iniciando ciclo de polling (long-poll)');

        while (this.running) {
            const executedTask = await this.runOnce();
            // Si se ejecutó una tarea, esperar antes de pedir la siguiente
            // (el servidor ya gestiona el tiempo de espera cuando no hay tareas)
            if (executedTask) {
                await new Promise(resolve => setTimeout(resolve, POST_TASK_DELAY_MS));
            }
        }
    }

    stop() {
        this.running = false;
        this.stopHeartbeat();
        this.log('Worker detenido');
    }
}

module.exports = WorkerRuntime;
