"""Tier B/C wow: simulador, plantillas CV, índice salarial, benchmarks, personas, clip."""

from __future__ import annotations

import re
from collections import Counter, defaultdict
from datetime import datetime, timezone
from statistics import median
from typing import Any
from urllib.parse import urlparse

from sqlalchemy import func, or_
from sqlalchemy.orm import Session

from app.models import Application, ClippedJob, Job, JobScore, Profile, SearchPersona, User
from app.scrapers.base import JobDTO
from app.services.scrape_service import recompute_scores_for_users, upsert_job

PROFILE_FIELDS = [
    "headline",
    "summary",
    "skills",
    "experience_years",
    "seniority",
    "languages",
    "english_fluent",
    "salary_min",
    "salary_currency",
    "remote_only",
    "target_countries",
    "keywords",
    "desired_roles",
    "job_categories",
    "employment_types",
    "timezone",
    "location",
    "phone",
    "linkedin_url",
    "portfolio_url",
    "work_authorization",
]


CV_TEMPLATES: list[dict[str, Any]] = [
    {
        "key": "fullstack_es",
        "sector": "tech",
        "name": "Full Stack (ES)",
        "title": "CV Full Stack",
        "content_md": (
            "# Nombre Apellido\nValencia · open to EU remote\n\n"
            "## Perfil\nDesarrollador/a full stack con foco producto SaaS.\n\n"
            "## Skills\nReact, TypeScript, Python, FastAPI, PostgreSQL, Docker, Git\n\n"
            "## Experiencia\n### Empresa — Full Stack (20XX–actual)\n"
            "- Entregué features end-to-end con impacto medible\n"
            "- Mejoré tiempos de deploy / calidad con CI\n\n"
            "## Formación\nGrado / bootcamp relevante\n"
        ),
        "wow": "Plantilla lista para gate de aprobación en 2 minutos.",
    },
    {
        "key": "devops",
        "sector": "tech",
        "name": "DevOps / SRE",
        "title": "CV DevOps",
        "content_md": (
            "# Nombre\n\n## Perfil\nDevOps/SRE: fiabilidad, IaC y observabilidad.\n\n"
            "## Skills\nKubernetes, Terraform, AWS, Linux, CI/CD, Prometheus, Python\n\n"
            "## Experiencia\n- Reduje MTTR / coste cloud\n- Plataforma self-service para equipos\n"
        ),
    },
    {
        "key": "comercial_b2b",
        "sector": "sales",
        "name": "Comercial B2B",
        "title": "CV Comercial B2B",
        "content_md": (
            "# Nombre\n\n## Perfil\nComercial B2B con cartera industrial / SaaS.\n\n"
            "## Skills\nNegociación, CRM, prospección, cierre, Excel\n\n"
            "## Logros\n- Cumplí/superé objetivo anual X%\n- Abrí N cuentas nuevas\n"
        ),
    },
    {
        "key": "enfermeria",
        "sector": "health",
        "name": "Enfermería",
        "title": "CV Enfermería",
        "content_md": (
            "# Nombre\nColegiación: XXX\n\n## Perfil\nEnfermero/a con experiencia en planta.\n\n"
            "## Competencias\nCuidados, medicación, trabajo en equipo, turnos\n\n"
            "## Experiencia\n### Hospital / Clínica\n- Gestión de pacientes y protocolos de seguridad\n"
        ),
    },
    {
        "key": "data_analyst",
        "sector": "tech",
        "name": "Data / Analytics",
        "title": "CV Data Analyst",
        "content_md": (
            "# Nombre\n\n## Perfil\nAnalista de datos orientado a negocio.\n\n"
            "## Skills\nSQL, Python, Power BI, Excel, storytelling\n\n"
            "## Proyectos\n- Dashboard que redujo tiempo de reporting\n"
        ),
    },
    {
        "key": "people_hr",
        "sector": "general",
        "name": "People / HRBP",
        "title": "CV People",
        "content_md": (
            "# Nombre\n\n## Perfil\nHRBP / Talent: selección y employee experience.\n\n"
            "## Skills\nEntrevistas, employer branding, ATS, coaching\n"
        ),
    },
]


def list_cv_templates() -> list[dict[str, Any]]:
    return [
        {
            "key": t["key"],
            "sector": t["sector"],
            "name": t["name"],
            "title": t["title"],
            "preview": t["content_md"][:220] + "…",
            "wow": t.get("wow", "Plantilla sectorial JobsWorld."),
        }
        for t in CV_TEMPLATES
    ]


def get_cv_template(key: str) -> dict[str, Any] | None:
    return next((t for t in CV_TEMPLATES if t["key"] == key), None)


def snapshot_profile(profile: Profile | None) -> dict[str, Any]:
    if not profile:
        return {}
    return {f: getattr(profile, f) for f in PROFILE_FIELDS}


def apply_snapshot(profile: Profile, snap: dict[str, Any]) -> None:
    for f in PROFILE_FIELDS:
        if f in snap:
            setattr(profile, f, snap[f])


def list_personas(db: Session, user: User) -> list[dict[str, Any]]:
    rows = db.query(SearchPersona).filter(SearchPersona.user_id == user.id).order_by(SearchPersona.id).all()
    return [
        {
            "id": p.id,
            "label": p.label,
            "relation": p.relation,
            "is_active": p.is_active or user.active_persona_id == p.id,
            "snapshot": p.snapshot or {},
            "updated_at": p.updated_at.isoformat() if p.updated_at else None,
        }
        for p in rows
    ]


def create_persona(
    db: Session,
    user: User,
    label: str,
    relation: str = "self",
    from_current: bool = True,
    snapshot: dict | None = None,
) -> dict[str, Any]:
    snap = snapshot if snapshot is not None else (snapshot_profile(user.profile) if from_current else {})
    p = SearchPersona(user_id=user.id, label=label or "Persona", relation=relation or "self", snapshot=snap)
    db.add(p)
    db.commit()
    db.refresh(p)
    return {"id": p.id, "label": p.label, "relation": p.relation, "is_active": False, "snapshot": p.snapshot}


def switch_persona(db: Session, user: User, persona_id: int) -> dict[str, Any]:
    p = db.query(SearchPersona).filter(SearchPersona.id == persona_id, SearchPersona.user_id == user.id).first()
    if not p:
        raise ValueError("Persona no encontrada")
    profile = user.profile
    if not profile:
        profile = Profile(user_id=user.id)
        db.add(profile)
        db.flush()
    # Guardar persona activa actual antes de cambiar
    if user.active_persona_id:
        cur = db.get(SearchPersona, user.active_persona_id)
        if cur and cur.user_id == user.id:
            cur.snapshot = snapshot_profile(profile)
            cur.is_active = False
    else:
        # auto-crea "Principal" si no hay
        if not db.query(SearchPersona).filter(SearchPersona.user_id == user.id).count():
            db.add(
                SearchPersona(
                    user_id=user.id,
                    label="Principal",
                    relation="self",
                    snapshot=snapshot_profile(profile),
                    is_active=False,
                )
            )
    apply_snapshot(profile, p.snapshot or {})
    for row in db.query(SearchPersona).filter(SearchPersona.user_id == user.id).all():
        row.is_active = row.id == p.id
    user.active_persona_id = p.id
    db.commit()
    recompute_scores_for_users(db)
    db.commit()
    return {
        "ok": True,
        "active_persona_id": p.id,
        "label": p.label,
        "profile": snapshot_profile(profile),
        "wow": "Cambio de persona en 1 clic — pareja/familia buscan sin mezclar CVs.",
    }


def save_current_to_active_persona(db: Session, user: User) -> None:
    if not user.active_persona_id or not user.profile:
        return
    p = db.get(SearchPersona, user.active_persona_id)
    if p and p.user_id == user.id:
        p.snapshot = snapshot_profile(user.profile)
        db.commit()


def interview_simulator_start(db: Session, user: User, job_id: int) -> dict[str, Any]:
    from app.services.intelligence import _fallback_interview

    job = db.get(Job, job_id)
    if not job:
        raise ValueError("Oferta no encontrada")
    # Heurística rápida (sin LLM) — la IA completa vive en Interview Copilot
    prep = _fallback_interview(job, user.profile)
    questions = prep.get("questions") or []
    if not questions:
        questions = [
            f"Preséntate en 60 segundos para «{job.title}».",
            "Cuenta un logro STAR relevante.",
            "¿Por qué esta empresa?",
            "¿Cómo gestionas el remoto/híbrido?",
            "Pregúntanos algo inteligente al final.",
        ]
    return {
        "job_id": job.id,
        "job_title": job.title,
        "company": job.company,
        "mode": "simulator",
        "questions": [{"i": i, "text": q} for i, q in enumerate(questions[:8])],
        "star_framework": prep.get("star_framework") or [],
        "voice_hint": "En el navegador puedes dictar (Web Speech) y oír la pregunta (speechSynthesis).",
        "wow": "Simulador de entrevista: practicas con la oferta real y recibes score al instante.",
    }


def score_interview_answer(
    question: str,
    answer: str,
    job: Job | None = None,
    profile: Profile | None = None,
) -> dict[str, Any]:
    text = (answer or "").strip()
    words = re.findall(r"\w+", text.lower())
    n = len(words)
    score = 20
    tips: list[str] = []
    if n >= 40:
        score += 20
    elif n >= 20:
        score += 12
    else:
        tips.append("Amplía la respuesta (~40–80 palabras) con contexto y resultado.")
    star_hits = sum(1 for k in ("situacion", "situation", "tarea", "task", "accion", "action", "result", "resultado", "logré", "impacto") if k in text.lower())
    score += min(25, star_hits * 8)
    if star_hits < 2:
        tips.append("Usa STAR: Situación → Tarea → Acción → Resultado medible.")
    skills = [str(s).lower() for s in (profile.skills if profile else []) or []]
    skill_hit = sum(1 for s in skills if s and s in text.lower())
    score += min(15, skill_hit * 5)
    if job:
        blob = f"{job.title} {' '.join(job.tags or [])}".lower()
        kw = [w for w in re.findall(r"[a-zA-Záéíóúñ]{4,}", blob) if w in text.lower()]
        score += min(15, len(set(kw)) * 3)
        if not kw:
            tips.append("Menciona keywords de la oferta (stack / dominio).")
    if "?" in question and len(text) < 10:
        score = min(score, 25)
        tips.append("La respuesta está demasiado corta para esa pregunta.")
    score = max(0, min(100, score))
    level = "Débil"
    if score >= 80:
        level = "Listo para entrevista real"
    elif score >= 60:
        level = "Sólido — pulir métricas"
    elif score >= 40:
        level = "En camino"
    return {
        "score": score,
        "level": level,
        "word_count": n,
        "tips": tips or ["Buen ritmo. Añade 1 métrica si puedes."],
        "model_answer_hint": (
            f"Estructura: contexto breve → tu acción con {(skills[:2] or ['tu skill clave'])} → "
            "resultado (%, €, tiempo) → aprendizaje."
        ),
    }


def salary_index_es(db: Session) -> dict[str, Any]:
    """Índice salarial público ES — lead magnet."""
    q = db.query(Job).filter(Job.salary_min.isnot(None))
    q = q.filter(
        or_(
            Job.location.ilike("%spain%"),
            Job.location.ilike("%españa%"),
            Job.location.ilike("%madrid%"),
            Job.location.ilike("%barcelona%"),
            Job.location.ilike("%valencia%"),
            Job.location.ilike("%sevilla%"),
            Job.location.ilike("%bilbao%"),
            Job.hire_from_spain_ok.is_(True),
        )
    )
    rows = q.order_by(Job.scraped_at.desc()).limit(2000).all()
    by_city: dict[str, list[int]] = defaultdict(list)
    by_role: dict[str, list[int]] = defaultdict(list)
    cities = ["valencia", "madrid", "barcelona", "sevilla", "remoto", "remote"]
    role_keys = [
        ("fullstack", ["full stack", "fullstack", "full-stack"]),
        ("backend", ["backend", "back-end", "python", "java"]),
        ("frontend", ["frontend", "front-end", "react"]),
        ("devops", ["devops", "sre", "platform"]),
        ("data", ["data", "analyst", "analytics"]),
        ("comercial", ["comercial", "account", "sales"]),
        ("salud", ["enfermer", "médic", "nurse"]),
    ]
    for j in rows:
        if (j.salary_currency or "EUR").upper() not in ("EUR", ""):
            continue
        loc = (j.location or "").lower()
        city = "otros"
        for c in cities:
            if c in loc or (c in ("remoto", "remote") and j.remote):
                city = "remoto" if c in ("remoto", "remote") else c
                break
        by_city[city].append(int(j.salary_min))
        title = (j.title or "").lower()
        for rk, keys in role_keys:
            if any(k in title for k in keys):
                by_role[rk].append(int(j.salary_min))
                break

    def band(vals: list[int]) -> dict[str, Any]:
        vals = sorted(vals)
        return {
            "n": len(vals),
            "p25": vals[max(0, len(vals) // 4)],
            "median": int(median(vals)),
            "p75": vals[min(len(vals) - 1, (3 * len(vals)) // 4)],
        }

    return {
        "generated_at": datetime.now(timezone.utc).isoformat(),
        "currency": "EUR",
        "sample_size": len(rows),
        "by_city": {k: band(v) for k, v in by_city.items() if len(v) >= 3},
        "by_role": {k: band(v) for k, v in by_role.items() if len(v) >= 3},
        "wow": "Índice salarial ES JobsWorld: lead magnet público con datos del propio agregador.",
        "cta": "Regístrate para Salary Intel personalizado + scripts de negociación.",
    }


def portal_benchmark(db: Session, user: User | None = None) -> dict[str, Any]:
    """Proxy de 'tasa de respuesta' por portal: apps aplicadas / jobs vistos en fuente."""
    job_counts = dict(db.query(Job.source, func.count(Job.id)).group_by(Job.source).all())
    app_q = db.query(Application, Job).join(Job, Application.job_id == Job.id)
    if user:
        app_q = app_q.filter(Application.user_id == user.id)
    apps = app_q.all()
    by_src: dict[str, Counter] = defaultdict(Counter)
    for app, job in apps:
        by_src[job.source][app.status or "saved"] += 1
    rows = []
    for src, total_jobs in sorted(job_counts.items(), key=lambda x: -x[1])[:40]:
        st = by_src.get(src) or Counter()
        applied = st.get("applied", 0) + st.get("applying", 0)
        pipeline = sum(st.values())
        response_proxy = round(100 * applied / pipeline, 1) if pipeline else None
        rows.append(
            {
                "source": src,
                "jobs_in_feed": total_jobs,
                "your_pipeline": pipeline,
                "applied_or_applying": applied,
                "response_proxy_pct": response_proxy,
                "statuses": dict(st),
            }
        )
    rows.sort(key=lambda r: (r["response_proxy_pct"] is not None, r["response_proxy_pct"] or -1), reverse=True)
    return {
        "rows": rows,
        "note": "Proxy local (tus candidaturas / fuente). No es tasa de respuesta del portal real.",
        "wow": "Benchmark de portales: ves dónde te conviene invertir tiempo de candidatura.",
    }


def remote_spain_graph(db: Session) -> dict[str, Any]:
    remote = db.query(Job).filter(Job.remote.is_(True)).count()
    spain_ok = db.query(Job).filter(Job.hire_from_spain_ok.is_(True)).count()
    both = db.query(Job).filter(Job.remote.is_(True), Job.hire_from_spain_ok.is_(True)).count()
    en_req = db.query(Job).filter(Job.requires_english_fluent.is_(True), Job.remote.is_(True)).count()
    by_source = (
        db.query(Job.source, func.count(Job.id))
        .filter(Job.remote.is_(True), or_(Job.hire_from_spain_ok.is_(True), Job.location.ilike("%spain%"), Job.location.ilike("%españa%"), Job.location.ilike("%eu%")))
        .group_by(Job.source)
        .order_by(func.count(Job.id).desc())
        .limit(15)
        .all()
    )
    return {
        "totals": {
            "remote": remote,
            "hire_from_spain_ok": spain_ok,
            "remote_and_spain_ok": both,
            "remote_english_required": en_req,
            "friendly_share_pct": round(100 * both / remote, 1) if remote else 0,
        },
        "top_sources": [{"source": s, "count": c} for s, c in by_source],
        "insight": (
            f"{both} ofertas remotas marcan contratación OK desde España. "
            f"{en_req} remotas exigen inglés fluido — filtra con tu perfil."
        ),
        "wow": "Mapa «quién contrata remote-from-Spain»: el niche que InfoJobs no enseña.",
    }


def clip_job(db: Session, user: User, payload: dict[str, Any]) -> dict[str, Any]:
    import hashlib

    url = (payload.get("url") or "").strip()
    if not url:
        raise ValueError("url requerida")
    title = (payload.get("title") or "").strip() or urlparse(url).netloc
    company = (payload.get("company") or "").strip()
    location = (payload.get("location") or "").strip()
    description = (payload.get("description") or "").strip()
    host = urlparse(url).netloc.replace("www.", "")[:40] or "clipper"
    external_id = "clip-" + hashlib.sha1(url.encode("utf-8")).hexdigest()[:16]
    dto = JobDTO(
        external_id=external_id,
        source="clipper",
        title=title[:500],
        company=company,
        location=location or "Clipped",
        remote="remote" in (location + description + title).lower(),
        url=url,
        description=description or f"Oferta guardada desde clipper ({host})",
        lang="es",
        tags=["clipped", host.split(".")[0]],
        hire_from_spain_ok=True,
        raw={"clipper": True, "host": host},
    )
    job, is_new = upsert_job(db, dto)
    clip = ClippedJob(
        user_id=user.id,
        url=url,
        title=title,
        company=company,
        location=location,
        description=description[:5000],
        source_hint=host,
        job_id=job.id if job else None,
    )
    db.add(clip)
    db.commit()
    if job:
        recompute_scores_for_users(db)
        db.commit()
    return {
        "ok": True,
        "clipped_id": clip.id,
        "job_id": job.id if job else None,
        "is_new_job": is_new,
        "wow": "Clipper: guarda cualquier oferta web en JobsWorld en 1 clic.",
    }


def telegram_status() -> dict[str, Any]:
    from app.core.config import get_settings

    s = get_settings()
    configured = bool(s.telegram_bot_token and (s.telegram_enabled or s.telegram_bot_token))
    return {
        "configured": bool(s.telegram_bot_token),
        "enabled": bool(s.telegram_enabled and s.telegram_bot_token),
        "hint": "Define TELEGRAM_BOT_TOKEN y TELEGRAM_ENABLED=true en .env; guarda chat_id en perfil.",
        "wow": "Alertas Telegram: el digest te encuentra fuera del portátil.",
    }


def telegram_send_test(db: Session, user: User) -> dict[str, Any]:
    import urllib.request
    import json

    from app.core.config import get_settings

    s = get_settings()
    if not s.telegram_bot_token:
        return {"ok": False, "detail": "Telegram no configurado (stub OK para demos)."}
    chat = user.telegram_chat_id
    if not chat:
        return {"ok": False, "detail": "Configura telegram_chat_id en el usuario."}
    text = f"JobsWorld OK · {user.name} · alertas listas."
    url = f"https://api.telegram.org/bot{s.telegram_bot_token}/sendMessage"
    body = json.dumps({"chat_id": chat, "text": text}).encode()
    req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            return {"ok": True, "status": resp.status}
    except Exception as exc:  # noqa: BLE001
        return {"ok": False, "detail": str(exc)}
