import "dotenv/config";
import express, { Request, Response, NextFunction } from "express";
import session from "express-session";
import MySQLStoreFactory from "express-mysql-session";
import bodyParser from "body-parser";
import cors from "cors";
import path from "path";
import rateLimit from "express-rate-limit";
import { connectDB } from "./config/db";
import pool from "./config/db";
import authRoutes from "./routes/authRoutes";
import propertyRoutes from "./routes/propertyRoutes";
import scraperRoutes from "./routes/scraperRoutes";
import filterRoutes from "./routes/filterRoutes";
import mapRoutes from "./routes/mapRoutes";
import zoneRoutes from "./routes/zoneRoutes";
import portalRoutes from "./routes/portalRoutes";
import portalLabRoutes from "./routes/portalLabRoutes";
import analyticsRoutes from "./routes/analyticsRoutes";
import municipalityRoutes from "./routes/municipalityRoutes";
import valenciaDistrictRoutes from "./routes/valenciaDistrictRoutes";
import aiRoutes from "./routes/aiRoutes";
import aiAdminRoutes from "./routes/aiAdminRoutes";
import brandRoutes from "./routes/brandRoutes";
import agentOpsRoutes from "./routes/agentOpsRoutes";
import healthRoutes from "./routes/healthRoutes";
import { requireAdmin } from "./middleware/requireAdmin";
import { requireAuth } from "./middleware/auth";
import { getActive, cssVariables } from "./services/brandService";
import { startScheduler, getSchedulerStatus, getRecentScrapingLogs } from "./services/schedulerService";
import { FilterModel } from "./models/filterModel";
import { PropertyModel } from "./models/propertyModel";
import { ZoneModel } from "./models/zoneModel";
import { PortalModel } from "./models/portalModel";
import { generatePngIcons } from "./utils/generateIcons";
import { VALENCIA_NEIGHBORHOODS } from "./config/valenciaNeighborhoods";
import { VALENCIA_DISTRICTS } from "./config/valenciaDistricts";
import { formatLocalIso, getApplicationTimezone } from "./config/time";
const app = express();
const PORT = Number(process.env.PORT) || 3000;
const isProduction = process.env.NODE_ENV === "production";
const sessionCookieSecure = process.env.SESSION_COOKIE_SECURE
  ? process.env.SESSION_COOKIE_SECURE === "true"
  : isProduction;

if (isProduction && (!process.env.SESSION_SECRET || process.env.SESSION_SECRET.length < 32)) {
  throw new Error("SESSION_SECRET debe existir y tener al menos 32 caracteres en producción");
}
if (process.env.TRUST_PROXY === "true") app.set("trust proxy", 1);

// MySQL session store (evita warning de MemoryStore en producción)
const MySQLStore = MySQLStoreFactory(session);
const sessionStore = new MySQLStore(
  { createDatabaseTable: true, clearExpired: true, checkExpirationInterval: 900000, expiration: 86400000 },
  (pool as any).pool
);

// Middleware
const allowedOrigins = new Set(
  (process.env.CORS_ORIGINS || "").split(",").map(value => value.trim()).filter(Boolean)
);
app.use(cors({
  credentials: true,
  origin: allowedOrigins.size === 0
    ? false
    : (origin, callback) => callback(null, !origin || allowedOrigins.has(origin)),
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use((_req, res, next) => {
  res.setHeader("X-Content-Type-Options", "nosniff");
  res.setHeader("X-Frame-Options", "DENY");
  res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
  res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=(self)");
  next();
});

// Comprobaciones públicas para supervisión y despliegues. Se montan antes de
// la sesión para que el health check no cree sesiones ni dependa del login.
app.use("/health", healthRoutes);

app.use(
  session({
    secret: process.env.SESSION_SECRET || "development-only-session-secret-change-me",
    store: sessionStore,
    resave: false,
    saveUninitialized: false,
    name: "captajaus.sid",
    cookie: {
      secure: sessionCookieSecure,
      httpOnly: true,
      sameSite: "lax",
      maxAge: 24 * 60 * 60 * 1000,
    },
  })
);

// Defensa CSRF incremental: rechaza mutaciones cross-origin cuando el navegador
// envía Origin/Referer. SameSite=Lax protege además la cookie de sesión.
app.use((req, res, next) => {
  if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next();
  const supplied = req.get("origin") || req.get("referer");
  if (!supplied) return next();
  try {
    if (new URL(supplied).host !== req.get("host")) {
      res.status(403).json({ success: false, error: "Origen no permitido" });
      return;
    }
  } catch {
    res.status(403).json({ success: false, error: "Origen no válido" });
    return;
  }
  next();
});

// Rate limiting para login (protección anti-bruteforce)
const loginRateMax = Number(process.env.AUTH_LOGIN_RATE_MAX || (process.env.NODE_ENV === "production" ? 10 : 60));
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: loginRateMax,
  message: "Demasiados intentos de inicio de sesión. Inténtalo en 15 minutos.",
  standardHeaders: true,
  legacyHeaders: false,
  skip: (req) => {
    const ip = String(req.ip || req.socket?.remoteAddress || "").replace(/^::ffff:/, "");
    return (
      ip === "127.0.0.1" ||
      ip.startsWith("192.168.") ||
      ip.startsWith("100.") ||
      ip.startsWith("10.")
    );
  },
});
app.use("/login", loginLimiter);

// View engine
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "..", "src", "views"));
if (process.env.NODE_ENV !== "production") app.set("view cache", false);

// Archivos estaticos
app.use(express.static(path.join(__dirname, "..", "public")));

app.use((req, res, next) => {
  res.locals.sessionRole = (req.session as { role?: string }).role || "";
  res.locals.sessionUser = (req.session as { username?: string }).username || "";
  try {
    const brand = getActive();
    res.locals.brandActive = brand;
    res.locals.brandCss = cssVariables(brand);
  } catch {
    res.locals.brandActive = null;
    res.locals.brandCss = "";
  }
  next();
});

// Redireccion raiz
app.get("/", (_req, res) => res.redirect("/login"));

// Rutas de autenticacion (sin proteccion)
app.use("/", authRoutes);
app.get("/login", (_req, res) => res.redirect("/login")); // ya cubierto por authRoutes

// Dashboard principal
app.get("/dashboard", requireAuth, async (req, res) => {
  try {
    const operation = String(req.query.op || "sale").toLowerCase() === "rent" ? "rent" : "sale";
    const [stats, portals, zones] = await Promise.all([
      PropertyModel.getStats(operation),
      PortalModel.findAll(),
      ZoneModel.findAll(),
    ]);
    res.render("dashboard/index", {
      portals,
      zones,
      user: (req.session as any).username || "George",
      count: stats.total,
      avg_price: stats.avg_price,
      min_price: stats.min_price,
      source_count: stats.source_count,
      operation,
      valencia_neighborhoods: VALENCIA_NEIGHBORHOODS,
    });
  } catch (err) {
    res.status(500).send("Error al cargar el dashboard");
  }
});

// Vista del archivo de propiedades eliminadas
app.get("/dashboard/archive", requireAuth, async (req, res) => {
  try {
    res.render("dashboard/archive", {
      user: (req.session as any).username || "George",
    });
  } catch (err) {
    res.status(500).send("Error al cargar el archivo");
  }
});

// Vista de filtros
app.get("/dashboard/filters", requireAuth, async (_req, res) => {
  try {
    const [filters, portals, zones] = await Promise.all([
      FilterModel.findAll(Number((_req.session as any).userId)),
      PortalModel.findAll(),
      ZoneModel.findActive(),
    ]);
    res.render("dashboard/filters", {
      filters,
      portals,
      zones,
      user: (_req.session as any).username || "George",
    });
  } catch (err) {
    res.status(500).send("Error al cargar filtros");
  }
});

// Vista de mapa
app.get("/dashboard/map", requireAuth, async (_req, res) => {
  try {
    const operation = String(_req.query.op || "sale").toLowerCase() === "rent" ? "rent" : "sale";
    const [props, zones] = await Promise.all([
      PropertyModel.findForMap(operation),
      ZoneModel.findAll(),
    ]);
    res.render("dashboard/map", {
      properties: props,
      zones,
      user: (_req.session as any).username || "George",
      operation,
    });
  } catch (err) {
    res.status(500).send("Error al cargar mapa");
  }
});

// Vista de zonas
app.get("/dashboard/zones", requireAuth, async (_req, res) => {
  try {
    res.render("dashboard/zones", {
      user: (_req.session as any).username || "George",
    });
  } catch (err) {
    res.status(500).send("Error al cargar zonas");
  }
});

// Vista IA — recomendaciones multiagent (operador George)
app.get("/dashboard/ai", requireAuth, async (req, res) => {
  if ((req.session as any).role === "ia_admin") {
    res.redirect("/dashboard/ai-admin");
    return;
  }
  const bridgeUrl = process.env.CURSOR_BRIDGE_URL || "http://100.78.198.98:8095";
  res.render("dashboard/ai", {
    user: (req.session as any).username || "George",
    bridgeUrl,
  });
});

// Portal IA avanzado — solo admin
app.get("/dashboard/ai-admin", requireAdmin, async (req, res) => {
  const bridgeUrl = process.env.CURSOR_BRIDGE_URL || "http://100.78.198.98:8095";
  res.render("dashboard/ai-admin", {
    user: (req.session as any).username || "admin",
    bridgeUrl,
  });
});

// Vista de portales
app.get("/dashboard/portals", requireAuth, async (_req, res) => {
  try {
    res.render("dashboard/portals", {
      user: (_req.session as any).username || "George",
    });
  } catch (err) {
    res.status(500).send("Error al cargar portales");
  }
});

// Portal Lab — testeo scrapers + IA supervisor
app.get("/dashboard/portal-lab", requireAuth, async (req, res) => {
  const bridgeUrl = process.env.CURSOR_BRIDGE_URL || "http://100.78.198.98:8095";
  res.render("dashboard/portal-lab", {
    user: (req.session as any).username || "George",
    bridgeUrl,
  });
});

// Ruta offline (Service Worker fallback)
app.get("/offline", (_req: Request, res: Response) => {
  res.render("offline");
});

// API routes
app.use("/api/properties", propertyRoutes);
app.use("/api/scraper", scraperRoutes);
app.use("/api/filters", filterRoutes);
app.use("/api/map", mapRoutes);
app.use("/api/zones", zoneRoutes);
app.use("/api/portals", portalRoutes);
app.use("/api/portals/lab", portalLabRoutes);
app.use("/api/municipalities", municipalityRoutes);
app.use("/api/valencia-districts", valenciaDistrictRoutes);
app.use("/api/ai", aiRoutes);
app.use("/api/ai-admin", aiAdminRoutes);
app.use("/api/brand", brandRoutes);
app.use("/api/agent", agentOpsRoutes);
app.use("/", analyticsRoutes);

// API: Estado del scheduler y logs de scraping
app.get("/api/scheduler/status", requireAuth, (_req: Request, res: Response) => {
  res.json({ success: true, data: getSchedulerStatus() });
});
app.get("/api/scheduler/logs", requireAuth, async (_req: Request, res: Response) => {
  const logs = await getRecentScrapingLogs(20);
  res.json({ success: true, data: logs });
});

// Inicio del servidor
const start = async () => {
  await connectDB();

  // Migración: añadir columna deleted_at si no existe (safe to run multiple times)
  try {
    await pool.query(`ALTER TABLE properties
      ADD COLUMN IF NOT EXISTS deleted_at DATETIME NULL DEFAULT NULL,
      ADD INDEX IF NOT EXISTS idx_deleted (deleted_at)`);
    await pool.query(`ALTER TABLE properties
      ADD COLUMN IF NOT EXISTS operation_type ENUM('sale','rent') NOT NULL DEFAULT 'sale',
      ADD INDEX IF NOT EXISTS idx_operation_type (operation_type)`);
    await pool.query(`CREATE TABLE IF NOT EXISTS valencia_district_slugs (
      id INT AUTO_INCREMENT PRIMARY KEY,
      name VARCHAR(120) NOT NULL UNIQUE,
      idealista_slug VARCHAR(160) NOT NULL,
      fotocasa_slug VARCHAR(160) NOT NULL,
      pisos_slug VARCHAR(160) NOT NULL,
      active TINYINT(1) DEFAULT 1,
      sort_order INT DEFAULT 999,
      created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )`);

    // La tabla se añadió después del init-db original y puede existir vacía.
    // Sembrar sólo cuando está vacía preserva altas/bajas hechas desde la UI.
    const [districtCountRows] = await pool.query(`SELECT COUNT(*) AS total FROM valencia_district_slugs`);
    const districtCount = Number((districtCountRows as any[])[0]?.total || 0);
    if (districtCount === 0) {
      for (const [index, district] of VALENCIA_DISTRICTS.entries()) {
        await pool.query(
          `INSERT IGNORE INTO valencia_district_slugs
           (name, idealista_slug, fotocasa_slug, pisos_slug, active, sort_order)
           VALUES (?, ?, ?, ?, 1, ?)`,
          [district.name, district.idealista, district.fotocasa, district.pisos, (index + 1) * 10]
        );
      }
      console.log(`[DB] Sembrados ${VALENCIA_DISTRICTS.length} distritos de Valencia.`);
    } else {
      // Migrar únicamente los slugs por defecto antiguos/incorrectos; no tocar
      // valores personalizados que el usuario haya configurado en la UI.
      const legacyPisosSlugs: Record<string, string[]> = {
        "Ciutat Vella": ["ciutat-vella-valencia-capital"],
        "Eixample": ["eixample-valencia-capital"],
        "Extramurs": ["extramurs-valencia-capital", "pisos-valencia_capital_extramurs"],
        "Campanar": ["campanar-valencia-capital"],
        "Patraix": ["patraix-valencia-capital", "pisos-valencia_capital_patraix"],
        "Quatre Carreres": ["quatre-carreres-valencia-capital", "pisos-valencia_capital_quatre_carreres"],
        "Poblats Maritims": ["poblats-maritims-valencia-capital", "pisos-valencia_capital_poblats_maritims"],
        "Camins al Grau": ["camins-al-grau-valencia-capital", "pisos-valencia_capital_camins_al_grau"],
        "Benicalap": ["benicalap-valencia-capital"],
        "Rascanya": ["rascanya-valencia-capital", "pisos-valencia_capital_rascanya"],
      };
      for (const district of VALENCIA_DISTRICTS) {
        const legacy = legacyPisosSlugs[district.name] || [];
        if (legacy.length === 0) continue;
        const placeholders = legacy.map(() => "?").join(", ");
        await pool.query(
          `UPDATE valencia_district_slugs SET pisos_slug = ?
           WHERE name = ? AND pisos_slug IN (${placeholders})`,
          [district.pisos, district.name, ...legacy]
        );
      }
    }
    console.log("[DB] Columna deleted_at verificada/añadida.");

    // Purga inicial: limpiar registros en archivo >30 días
    const { PropertyModel: PM } = await import("./models/propertyModel");
    const purged = await PM.purgeOldDeleted();
    if (purged > 0) console.log(`[DB] Purgados ${purged} anuncios del archivo (>30 días).`);
  } catch (e: any) {
    console.warn("[DB] Migración deleted_at:", e.message);
  }

  // Generar iconos PNG para PWA (desde icon.svg)
  await generatePngIcons().catch((e) => console.warn("[Icons]", e.message));
  app.listen(PORT, () => {
    console.log(`Servidor arrancado en http://localhost:${PORT}`);
    console.log(`Dashboard: http://localhost:${PORT}/dashboard`);
    console.log(`[Time] ${formatLocalIso(new Date(), getApplicationTimezone())}; API/health usa ISO UTC`);
  });
  if (process.env.SCHEDULER_ENABLED === "false") {
    console.warn("[Scheduler] Desactivado mediante SCHEDULER_ENABLED=false");
  } else {
    startScheduler();
  }
};

start().catch((err) => {
  console.error("Error al iniciar:", err);
  process.exit(1);
});

// Error handler centralizado (debe ir DESPUÉS de todas las rutas)
app.use((_req: Request, res: Response) => {
  res.status(404).render("error", { message: "Página no encontrada (404)", user: null });
});

// eslint-disable-next-line @typescript-eslint/no-unused-vars
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
  console.error("[Error Handler]", err.message, err.stack);
  res.status(500).render("error", { message: err.message || "Error interno del servidor", user: null });
});

export default app;
