/**
 * Cola de propuestas de negocio (lab propone → humano acepta → Bridge ejecuta).
 */
import fs from "fs";
import path from "path";
import { getActive } from "./brandService";
import { executePrompt, newProposalId } from "./bridgeExecService";
import { dispatchChat } from "./supervisorAiService";

export const RISK_TIERS = ["L0", "L1", "L2", "L3", "L4"] as const;
export const KINDS = [
  "marketing_campaign",
  "brand_swap",
  "code_fix",
  "ops",
  "pricing",
  "product",
] as const;

export type RiskTier = (typeof RISK_TIERS)[number];
export type ProposalKind = (typeof KINDS)[number];
export type ProposalStatus = "proposed" | "accepted" | "executing" | "done" | "failed" | "rejected";

export interface Proposal {
  id: string;
  app: string;
  kind: ProposalKind | string;
  title: string;
  risk_tier: RiskTier | string;
  impact_kpi: string[];
  prompt_for_bridge: string;
  files_hint: string[];
  rationale: string;
  source: string;
  status: ProposalStatus;
  created_at: string;
  updated_at: string;
  execution?: {
    at: string;
    bridge: Record<string, unknown>;
  } | null;
  reject_reason?: string;
}

const STORE = path.join(process.cwd(), "data", "proposals.json");

function ensureStore(): void {
  if (!fs.existsSync(STORE)) {
    const dir = path.dirname(STORE);
    if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
    fs.writeFileSync(STORE, "[]", "utf8");
  }
}

function load(): Proposal[] {
  ensureStore();
  try {
    const data = JSON.parse(fs.readFileSync(STORE, "utf8"));
    return Array.isArray(data) ? (data as Proposal[]) : [];
  } catch {
    return [];
  }
}

function save(items: Proposal[]): void {
  ensureStore();
  fs.writeFileSync(STORE, JSON.stringify(items, null, 2), "utf8");
}

export function listProposals(status?: string): Proposal[] {
  let items = load();
  if (status) items = items.filter((i) => i.status === status);
  return items.sort((a, b) => (b.created_at || "").localeCompare(a.created_at || ""));
}

export function getProposal(pid: string): Proposal | null {
  return load().find((i) => i.id === pid) || null;
}

function upsert(item: Proposal): Proposal {
  const items = load();
  const idx = items.findIndex((i) => i.id === item.id);
  if (idx >= 0) items[idx] = item;
  else items.unshift(item);
  save(items);
  return item;
}

export function createProposal(input: {
  title: string;
  kind?: string;
  risk_tier?: string;
  prompt_for_bridge: string;
  impact_kpi?: string[];
  files_hint?: string[];
  rationale?: string;
  source?: string;
}): Proposal {
  const kind = KINDS.includes(input.kind as ProposalKind) ? input.kind! : "product";
  const risk = RISK_TIERS.includes(input.risk_tier as RiskTier) ? input.risk_tier! : "L1";
  const now = new Date().toISOString();
  const item: Proposal = {
    id: newProposalId(),
    app: "captajaus",
    kind,
    title: input.title.trim().slice(0, 200),
    risk_tier: risk,
    impact_kpi: input.impact_kpi || ["listings_captured", "alert_conversions"],
    prompt_for_bridge: input.prompt_for_bridge.trim(),
    files_hint: input.files_hint || [],
    rationale: (input.rationale || "").trim().slice(0, 2000),
    source: input.source || "lab_agent",
    status: "proposed",
    created_at: now,
    updated_at: now,
    execution: null,
  };
  return upsert(item);
}

export function setStatus(pid: string, status: ProposalStatus, extra?: Partial<Proposal>): Proposal {
  const item = getProposal(pid);
  if (!item) throw new Error("Propuesta no encontrada");
  item.status = status;
  item.updated_at = new Date().toISOString();
  if (extra) Object.assign(item, extra);
  return upsert(item);
}

export function reject(pid: string, reason = ""): Proposal {
  return setStatus(pid, "rejected", { reject_reason: reason });
}

export async function acceptAndExecute(pid: string, wait = false): Promise<Proposal> {
  const item = getProposal(pid);
  if (!item) throw new Error("Propuesta no encontrada");
  if (!["proposed", "accepted", "failed"].includes(item.status)) {
    throw new Error(`estado inválido: ${item.status}`);
  }

  const risk = item.risk_tier || "L1";
  if (risk === "L4") throw new Error("L4 (dinero/legal) no se auto-ejecuta");

  const brandPack = getActive();
  const prompt = [
    "MODO EJECUCIÓN CaptaJaus — propuesta aceptada por admin.",
    `Marca activa: ${brandPack.name} (${brandPack.id}).`,
    `Risk: ${risk}. Kind: ${item.kind}.`,
    `Archivos sugeridos: ${(item.files_hint || []).join(", ") || "los que toquen"}.`,
    "Propón diffs concretos (propose). No inventes secretos ni credenciales.",
    "",
    "## Título",
    item.title,
    "",
    "## Rationale",
    item.rationale,
    "",
    "## Tarea",
    item.prompt_for_bridge,
  ].join("\n");

  setStatus(pid, "executing");
  const result = await executePrompt(prompt, { wait });
  const ok = Boolean(result.ok);
  const finalStatus: ProposalStatus = ok ? (wait ? "done" : "executing") : "failed";
  return setStatus(pid, finalStatus, {
    execution: {
      at: new Date().toISOString(),
      bridge: { ...result } as Record<string, unknown>,
    },
  });
}

const SEED_PROMPTS: Omit<Parameters<typeof createProposal>[0], "source">[] = [
  {
    title: "Scraper: anti-ban Idealista/Fotocasa Llíria+Valencia",
    kind: "code_fix",
    risk_tier: "L2",
    impact_kpi: ["listings_captured", "scraper_uptime"],
    files_hint: ["src/scrapers/", "src/services/scrapeRunCoordinator.ts"],
    rationale: "Portales ES bloquean bots; necesitamos rotación de user-agent, delays y recuperación.",
    prompt_for_bridge:
      "Revisa scrapers Idealista/Fotocasa/Pisos para Llíria y Valencia: anti-ban Playwright, " +
      "reintentos, logging en scrapeRunCoordinator. Diff mínimo, sin romper scheduler PM2.",
  },
  {
    title: "Marketing: landing agencia Llíria con CTA alertas",
    kind: "marketing_campaign",
    risk_tier: "L1",
    impact_kpi: ["alert_signups", "whatsapp_clicks"],
    files_hint: ["src/views/dashboard/index.ejs", "public/css/main.css"],
    rationale: "Captación local: agencias quieren leads de compradores en comarca.",
    prompt_for_bridge:
      "Mejora dashboard/index con héroe CaptaJaus/LlíriaProps: valor (alertas email, mapa, scoring), " +
      "CTA claro a filtros y registro. Usa BrandPack CSS vars (--brand-*). Responsive móvil.",
  },
  {
    title: "Brand swap: preset activo en navbar + PWA",
    kind: "brand_swap",
    risk_tier: "L0",
    impact_kpi: ["brand_consistency"],
    files_hint: ["src/views/layouts/main.ejs", "public/manifest.json", "data/brand/"],
    rationale: "White-label partner: nombre, colores y theme-color deben seguir preset activo.",
    prompt_for_bridge:
      "Conecta BrandPack activo a main.ejs y manifest.json: título, theme-color, logo y navbar " +
      "deben leer data/brand/active.json. No hardcodear LlíriaProps si hay otro preset.",
  },
  {
    title: "Ops: deduplicación y scoring tras scrape masivo",
    kind: "ops",
    risk_tier: "L2",
    impact_kpi: ["duplicate_rate", "quality_score"],
    files_hint: ["src/services/deduplicationService.ts", "src/services/scoringService.ts"],
    rationale: "Tras ampliar Valencia, suben duplicados entre portales.",
    prompt_for_bridge:
      "Audita deduplicationService y scoringService: reglas por URL/hash, precio/m², zona. " +
      "Propón job post-scrape en schedulerService con métricas en analytics.",
  },
  {
    title: "Producto: alertas WhatsApp para nuevas oportunidades",
    kind: "product",
    risk_tier: "L3",
    impact_kpi: ["alert_conversions", "time_to_contact"],
    files_hint: ["src/services/emailService.ts", "src/routes/filterRoutes.ts"],
    rationale: "Agentes responden más rápido por WhatsApp que solo email.",
    prompt_for_bridge:
      "Diseña MVP alertas WhatsApp (Twilio o webhook) paralelo a emailService para filtros activos. " +
      "Documenta env vars en .env.example sin secretos.",
  },
];

export async function generateFromLab(useLlm = true, count = 4): Promise<Proposal[]> {
  const created: Proposal[] = [];

  if (useLlm) {
    const active = getActive();
    const system =
      "Eres estratega de producto de un scraper inmobiliario Llíria/Valencia (CaptaJaus). " +
      "Responde SOLO JSON array de 3-5 objetos con keys: " +
      "title, kind, risk_tier, impact_kpi, files_hint, rationale, prompt_for_bridge. " +
      "kind in marketing_campaign|product|pricing|ops|code_fix|brand_swap. risk_tier L0-L3. " +
      "Objetivo: más captaciones, alertas y calidad de datos para agencias.";
    const user =
      `Marca: ${active.name}. Tagline: ${active.tagline}. ` +
      "Portales: Idealista, Fotocasa, Pisos. Zonas: Llíria, Valencia capital. " +
      "Propón acciones concretas ejecutables por un agente Cursor en el repo captajaus.";

    try {
      const res = await dispatchChat(`${system}\n\n${user}`, "research");
      if (res.ok && res.data) {
        const raw = res.data as Record<string, unknown>;
        const text = String(raw.message || raw.reply || raw.content || JSON.stringify(raw));
        let parsed = text.trim();
        if (parsed.startsWith("```")) {
          parsed = parsed.replace(/^```(?:json)?\s*/i, "").replace(/```\s*$/, "").trim();
        }
        const start = parsed.indexOf("[");
        const end = parsed.lastIndexOf("]");
        if (start >= 0 && end > start) {
          const arr = JSON.parse(parsed.slice(start, end + 1)) as unknown[];
          if (Array.isArray(arr)) {
            for (const obj of arr.slice(0, count)) {
              if (!obj || typeof obj !== "object" || !("title" in obj)) continue;
              const o = obj as Record<string, unknown>;
              created.push(
                createProposal({
                  title: String(o.title),
                  kind: String(o.kind || "product"),
                  risk_tier: String(o.risk_tier || "L1"),
                  prompt_for_bridge: String(o.prompt_for_bridge || o.title),
                  impact_kpi: Array.isArray(o.impact_kpi) ? o.impact_kpi.map(String) : [],
                  files_hint: Array.isArray(o.files_hint) ? o.files_hint.map(String) : [],
                  rationale: String(o.rationale || ""),
                  source: "lab_supervisor",
                })
              );
            }
          }
        }
      }
    } catch {
      /* fallback to seeds */
    }
  }

  if (created.length === 0) {
    for (const seed of SEED_PROMPTS.slice(0, count)) {
      created.push(createProposal({ ...seed, source: "seed_playbook" }));
    }
  }
  return created;
}

export function ensureSeedIfEmpty(): Proposal[] {
  const items = load();
  if (items.length > 0) return items;
  return SEED_PROMPTS.slice(0, 4).map((s) => createProposal({ ...s, source: "seed_playbook" }));
}
