/**
 * Capa de aprendizaje operativo: señales del scraper → propuestas → RAG → agentes código.
 * Persistencia local en data/ai-learning/ (sin dependencia de prod).
 */
import fs from "fs/promises";
import path from "path";
import pool from "../config/db";
import { RowDataPacket } from "mysql2";
import PropertyService from "./propertyService";
import { PortalModel } from "../models/portalModel";
import { getSchedulerStatus } from "./schedulerService";
import { recommendTask, buildBridgeDeepLink } from "./supervisorAiService";

const DATA_DIR = path.join(process.cwd(), "data", "ai-learning");
const FEEDBACK_FILE = path.join(DATA_DIR, "feedback.json");
const SNAPSHOT_FILE = path.join(DATA_DIR, "last-snapshot.json");

export interface AiProposal {
  id: string;
  priority: "high" | "medium" | "low";
  agent: "code" | "ops" | "research";
  title: string;
  reason: string;
  task: string;
  signals: string[];
}

export interface FeedbackEntry {
  proposalId: string;
  rating: 1 | -1;
  note?: string;
  at: string;
}

async function ensureDataDir(): Promise<void> {
  await fs.mkdir(DATA_DIR, { recursive: true });
}

async function readJson<T>(file: string, fallback: T): Promise<T> {
  try {
    const raw = await fs.readFile(file, "utf8");
    return JSON.parse(raw) as T;
  } catch {
    return fallback;
  }
}

async function writeJson(file: string, data: unknown): Promise<void> {
  await ensureDataDir();
  await fs.writeFile(file, JSON.stringify(data, null, 2), "utf8");
}

/** Señales operativas: portales obsoletos, baja cobertura, duplicados, scheduler. */
export async function collectOperationalSnapshot(): Promise<Record<string, unknown>> {
  const [analytics, portals, staleRows, zeroRows] = await Promise.all([
    PropertyService.getAnalytics(),
    PortalModel.findAll(),
    pool.query<RowDataPacket[]>(`
      SELECT source, COUNT(*) AS cnt, MAX(COALESCE(last_seen_at, updated_at)) AS last_update,
             TIMESTAMPDIFF(HOUR, MAX(COALESCE(last_seen_at, updated_at)), NOW()) AS hours_stale
      FROM properties
      GROUP BY source
      HAVING hours_stale > 48 OR cnt < 5
      ORDER BY hours_stale DESC
    `),
    pool.query<RowDataPacket[]>(`
      SELECT p.name AS portal, p.scraper_class, p.active
      FROM portals p
      LEFT JOIN properties pr ON pr.source = p.name
      WHERE p.active = 1
      GROUP BY p.id, p.name, p.scraper_class, p.active
      HAVING COUNT(pr.id) = 0
    `),
  ]);

  const snapshot = {
    collected_at: new Date().toISOString(),
    scheduler: getSchedulerStatus(),
    analytics_summary: analytics.summary,
    portals_active: portals.filter((p) => p.active).length,
    portals_total: portals.length,
    stale_sources: staleRows[0],
    empty_active_portals: zeroRows[0],
    by_portal: analytics.byPortal,
    price_drops: analytics.priceDrops,
  };

  await writeJson(SNAPSHOT_FILE, snapshot);
  return snapshot;
}

function proposalId(seed: string): string {
  return seed.replace(/[^a-z0-9]+/gi, "-").toLowerCase().slice(0, 48);
}

/** Heurísticas + historial de feedback → propuestas priorizadas. */
export async function generateProposals(snapshot?: Record<string, unknown>): Promise<AiProposal[]> {
  const snap = snapshot || (await collectOperationalSnapshot());
  const feedback = await readJson<FeedbackEntry[]>(FEEDBACK_FILE, []);
  const rejected = new Set(feedback.filter((f) => f.rating === -1).map((f) => f.proposalId));

  const proposals: AiProposal[] = [];

  for (const row of (snap.stale_sources as RowDataPacket[]) || []) {
    const hours = Number(row.hours_stale) || 0;
    const id = proposalId(`stale-${row.source}`);
    if (rejected.has(id)) continue;
    proposals.push({
      id,
      priority: hours > 120 ? "high" : "medium",
      agent: "code",
      title: `Portal ${row.source} sin actualizar (${hours}h)`,
      reason: `Última actualización hace ${hours}h con ${row.cnt} anuncios.`,
      task: `Revisa scraper de ${row.source}: logs PM2, timeouts Playwright, selectores rotos. Propón fix mínimo en modo revisión.`,
      signals: [`stale:${row.source}`, `hours:${hours}`],
    });
  }

  for (const row of (snap.empty_active_portals as RowDataPacket[]) || []) {
    const id = proposalId(`empty-${row.portal}`);
    if (rejected.has(id)) continue;
    proposals.push({
      id,
      priority: "high",
      agent: "code",
      title: `Portal activo sin datos: ${row.portal}`,
      reason: `${row.scraper_class} está activo pero 0 propiedades en BD.`,
      task: `Diagnostica por qué ${row.portal} (${row.scraper_class}) no ingesta datos. Revisa URL zona, anti-ban y parser.`,
      signals: [`empty_portal:${row.portal}`],
    });
  }

  const summary = snap.analytics_summary as Record<string, number> | undefined;
  const dupRate = summary && summary.total
    ? Number(summary.duplicates || 0) / Number(summary.total)
    : 0;
  if (dupRate > 0.15) {
    const id = "dedup-high-rate";
    if (!rejected.has(id)) {
      proposals.push({
        id,
        priority: "medium",
        agent: "code",
        title: "Tasa de duplicados elevada",
        reason: `${Math.round(dupRate * 100)}% marcados como duplicados (${summary?.duplicates}/${summary?.total}).`,
        task: "Audita deduplicationService: falsos positivos, umbrales geográficos y normalización de títulos.",
        signals: [`dup_rate:${dupRate.toFixed(2)}`],
      });
    }
  }

  const priceDrops = snap.price_drops as { count?: number } | undefined;
  if (priceDrops && Number(priceDrops.count) > 0) {
    const id = "price-drop-alerts";
    if (!rejected.has(id)) {
      proposals.push({
        id,
        priority: "low",
        agent: "research",
        title: "Oportunidades por bajadas de precio",
        reason: `${priceDrops.count} bajadas detectadas en historial.`,
        task: "Analiza price_history y propón mejoras en scoring/alertas email para captar oportunidades.",
        signals: ["price_drops"],
      });
    }
  }

  // Refuerzo por feedback positivo (re-priorizar temas aceptados)
  const liked = feedback.filter((f) => f.rating === 1).slice(-5);
  for (const like of liked) {
    const existing = proposals.find((p) => p.id === like.proposalId);
    if (existing) existing.priority = "high";
  }

  const order = { high: 0, medium: 1, low: 2 };
  proposals.sort((a, b) => order[a.priority] - order[b.priority]);
  return proposals.slice(0, 8);
}

/** Combina propuestas locales con recomendación del supervisor multiagent. */
export async function learnAndRecommend(): Promise<{
  snapshot: Record<string, unknown>;
  proposals: AiProposal[];
  supervisor?: Record<string, unknown>;
}> {
  const snapshot = await collectOperationalSnapshot();
  const proposals = await generateProposals(snapshot);

  let supervisor: Record<string, unknown> | undefined;
  if (proposals.length > 0) {
    const top = proposals[0];
    const rec = await recommendTask(top.task, top.agent);
    if (rec.ok && rec.data) supervisor = rec.data as Record<string, unknown>;
  }

  return { snapshot, proposals, supervisor };
}

export async function recordFeedback(
  proposalId: string,
  rating: 1 | -1,
  note?: string
): Promise<FeedbackEntry[]> {
  const list = await readJson<FeedbackEntry[]>(FEEDBACK_FILE, []);
  list.push({ proposalId, rating, note, at: new Date().toISOString() });
  await writeJson(FEEDBACK_FILE, list.slice(-200));
  return list;
}

/** Prompt estructurado para agente código vía Cursor Bridge (modo revisión). */
export function buildEvolvePrompt(proposal: AiProposal, snapshot: Record<string, unknown>): string {
  const stale = ((snapshot.stale_sources as RowDataPacket[]) || [])
    .map((r) => `${r.source}:${r.hours_stale}h`)
    .join(", ");
  return [
    "MODO REVISIÓN — Captajaus evolve (agente puede editar código tras confirmación).",
    `Prioridad: ${proposal.priority} · Agente sugerido: ${proposal.agent}`,
    "",
    `## Objetivo`,
    proposal.title,
    "",
    `## Contexto operativo`,
    `- Portales activos: ${snapshot.portals_active}/${snapshot.portals_total}`,
    stale ? `- Fuentes obsoletas: ${stale}` : "",
    `- Señales: ${proposal.signals.join(", ")}`,
    "",
    `## Tarea`,
    proposal.task,
    "",
    "Entrega: diff mínimo en TypeScript, comandos QA (npm run build), riesgos anti-ban.",
  ]
    .filter(Boolean)
    .join("\n");
}

export async function evolveProposal(proposalId: string): Promise<{
  proposal: AiProposal;
  evolvePrompt: string;
  bridgeUrl: string;
  snapshot: Record<string, unknown>;
}> {
  const snapshot = await readJson<Record<string, unknown>>(SNAPSHOT_FILE, {});
  const proposals = await generateProposals(
    Object.keys(snapshot).length ? snapshot : await collectOperationalSnapshot()
  );
  const proposal = proposals.find((p) => p.id === proposalId);
  if (!proposal) {
    throw new Error(`Propuesta no encontrada: ${proposalId}`);
  }
  const evolvePrompt = buildEvolvePrompt(proposal, snapshot);
  return {
    proposal,
    evolvePrompt,
    bridgeUrl: buildBridgeDeepLink(evolvePrompt),
    snapshot,
  };
}

/** Ingesta insights operativos al RAG del supervisor (memoria del proyecto). */
export async function buildInsightsDocument(
  snapshot: Record<string, unknown>,
  proposals: AiProposal[]
): Promise<string> {
  const lines = [
    `# Captajaus — insights operativos ${snapshot.collected_at || new Date().toISOString()}`,
    "",
    "## Resumen",
    JSON.stringify(snapshot.analytics_summary, null, 2),
    "",
    "## Propuestas activas",
    ...proposals.map(
      (p) => `- [${p.priority}] ${p.title}: ${p.reason}`
    ),
    "",
    "## Portales obsoletos",
    JSON.stringify(snapshot.stale_sources, null, 2),
  ];
  return lines.join("\n");
}
