import { Request, Response } from "express";
import { PropertyService } from "../services/propertyService";
import { EmailService } from "../services/emailService";
import { getSchedulerStatus } from "../services/schedulerService";
import { PortalModel } from "../models/portalModel";
import { scraperBus, busLog, ScraperLogEvent, ScraperProgressEvent } from "../utils/scraperEventBus";
import { scrapeRunCoordinator } from "../services/scrapeRunCoordinator";

const propService = new PropertyService();
const emailService = new EmailService();

function scrapeStatus() {
  const state = scrapeRunCoordinator.status();
  return {
    ...state,
    manualRunning: state.mode === "manual",
    scheduledRunning: state.mode === "scheduled",
  } as const;
}

export const scraperController = {
  parseOperation(req: Request): "sale" | "rent" {
    const op = String(req.query.op || req.params.operation || "sale").toLowerCase();
    return op === "rent" ? "rent" : "sale";
  },
  scrapeNow: async (req: Request, res: Response): Promise<void> => {
    if (!scrapeRunCoordinator.tryStart("manual")) {
      res.status(409).json({ success: false, message: "Scraping ya en ejecucion" });
      return;
    }
    res.status(202).json({ success: true, message: "Scraping iniciado en segundo plano" });
    scraperBus.clearLogs();
    busLog("info", "Iniciando scraping completo de todos los portales activos...");
    const operation = scraperController.parseOperation(req);
    propService.scrapeAll(operation)
      .then(result => busLog("success", `Completado: ${result.new_count} nuevas / ${result.total} total`))
      .catch(err => busLog("error", `Error scraping: ${(err as Error).message}`))
      .finally(() => { scrapeRunCoordinator.finish("manual"); });
  },

  status: (_req: Request, res: Response): void => {
    res.json({ success: true, scheduler: getSchedulerStatus(), ...scrapeStatus() });
  },

  sendAlerts: async (req: Request, res: Response): Promise<void> => {
    try {
      await emailService.sendFilteredAlerts();
      res.json({ success: true, message: "Alertas enviadas" });
    } catch (err) {
      res.status(500).json({ success: false, error: "Error al enviar alertas" });
    }
  },

  scrapeByPortal: async (req: Request, res: Response): Promise<void> => {
    const { slug } = req.params;
    const portal = await PortalModel.findBySlug(slug);
    if (!portal) {
      res.status(404).json({ success: false, error: `Portal '${slug}' no encontrado` });
      return;
    }
    if (!scrapeRunCoordinator.tryStart("manual")) {
      res.status(409).json({ success: false, message: "Scraping ya en ejecucion" });
      return;
    }
    res.status(202).json({ success: true, message: `Escaneo de ${portal.name} iniciado en segundo plano` });
    scraperBus.clearLogs();
    const operation = scraperController.parseOperation(req);
    propService.scrapePortal(portal.scraper_class, operation)
      .then(r => busLog("success", `${portal.name}: ${r.new_count} nuevas / ${r.total} total`))
      .catch(err => busLog("error", `Error scraping ${portal.name}: ${(err as Error).message}`))
      .finally(() => { scrapeRunCoordinator.finish("manual"); });
  },

  scrapeByZone: async (req: Request, res: Response): Promise<void> => {
    const { slug } = req.params;
    if (!scrapeRunCoordinator.tryStart("manual")) {
      res.status(409).json({ success: false, message: "Scraping ya en ejecución" });
      return;
    }
    res.status(202).json({ success: true, message: `Escaneo de zona '${slug}' iniciado en segundo plano` });
    scraperBus.clearLogs();
    const operation = scraperController.parseOperation(req);
    propService.scrapeByZone(slug, operation)
      .then(r => busLog("success", `Zona ${slug}: ${r.new_count} nuevas / ${r.total} total`))
      .catch(err => busLog("error", `Error scraping zona ${slug}: ${(err as Error).message}`))
      .finally(() => { scrapeRunCoordinator.finish("manual"); });
  },

  reGeocode: async (_req: Request, res: Response): Promise<void> => {
    try {
      res.json({ success: true, message: "Re-geocodificación iniciada en segundo plano" });
      busLog("info", "Re-geocodificación iniciada...");
      propService.reGeocodeAll()
        .then(r => busLog("success", `Re-geocodificación completada: ${r.updated} actualizadas`))
        .catch(e => busLog("error", `Error re-geocodificando: ${(e as Error).message}`));
    } catch (err) {
      res.status(500).json({ success: false, error: "Error al iniciar re-geocodificación" });
    }
  },

  /**
   * GET /api/scraper/logs/stream
   * SSE: stream de logs en tiempo real.
   */
  streamLogs: (req: Request, res: Response): void => {
    res.setHeader("Content-Type", "text/event-stream");
    res.setHeader("Cache-Control", "no-cache");
    res.setHeader("Connection", "keep-alive");
    res.setHeader("X-Accel-Buffering", "no");
    res.flushHeaders();

    const sendStatus = () => {
      res.write(`event: status\ndata: ${JSON.stringify({ type: "status", ...scrapeStatus() })}\n\n`);
    };

    // Enviar buffer reciente al conectarse
    for (const log of scraperBus.recent(200)) {
      res.write(`event: log\ndata: ${JSON.stringify({ type: "log", ...log })}\n\n`);
    }
    sendStatus();

    const onLog = (ev: ScraperLogEvent) => {
      res.write(`event: log\ndata: ${JSON.stringify({ type: "log", ...ev })}\n\n`);
    };
    const onProgress = (ev: ScraperProgressEvent) => {
      res.write(`event: progress\ndata: ${JSON.stringify({ type: "progress", ...ev })}\n\n`);
    };

    const heartbeat = setInterval(() => {
      res.write(`:heartbeat\n\n`);
      sendStatus();
    }, 20000);

    scraperBus.on("log", onLog);
    scraperBus.on("progress", onProgress);

    req.on("close", () => {
      scraperBus.off("log", onLog);
      scraperBus.off("progress", onProgress);
      clearInterval(heartbeat);
    });
  },

  /** GET /api/scraper/logs — versión JSON polling */
  getLogs: (_req: Request, res: Response): void => {
    res.json({ ...scrapeStatus(), logs: scraperBus.recent(200) });
  },
};

export default scraperController;
