"""Lanzamiento real: redes, landings temáticas, kits UTM y compose nativo.

No simula APIs de Meta/LinkedIn (requieren app review). Genera URLs de
composer, landings indexables y un kit que el admin puede publicar hoy.
"""

from __future__ import annotations

import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from urllib.parse import quote, urlencode

from app.core.config import get_settings

LANDINGS: list[dict[str, Any]] = [
    {
        "slug": "remoto-espana",
        "kicker": "Remoto desde España",
        "title": "Trabajo remoto que sí contrata desde España",
        "lede": "Filtros de inglés no fluido, ventana de oro de 24 h y CV con tu visto bueno. Empieza gratis.",
        "cta": "Ver ofertas remotas",
        "path": "/login",
        "hooks": [
            "Ni InfoJobs ni LinkedIn te marcan si el anuncio lleva 45 días muerto.",
            "Profesional 14,90 €: detector de fantasmas + anti-silencio.",
        ],
    },
    {
        "slug": "valencia",
        "kicker": "Valencia",
        "title": "Empleo en Valencia, no otro portal de Madrid",
        "lede": "Híbrido, presencial y remoto con sueldo traducido a poder de compra local.",
        "cta": "Buscar en Valencia",
        "path": "/login",
        "hooks": [
            "38.000 € en Valencia no es lo mismo que en Madrid. Te lo decimos.",
            "Portales ES reales, no un tracker USA.",
        ],
    },
    {
        "slug": "ofertas-fantasma",
        "kicker": "Detector",
        "title": "Deja de aplicar a anuncios que nunca van a responder",
        "lede": "JobsWorld señala bolsas eternas, talent pools y plazas repetidas en cuatro portales.",
        "cta": "Probar el detector",
        "path": "/pricing",
        "hooks": [
            "Función de Profesional: no está en Teal, Huntr ni InfoJobs.",
            "Oculta sospechosas en un clic.",
        ],
    },
    {
        "slug": "cv-ia",
        "kicker": "Currículum",
        "title": "La IA reescribe tu CV. Tú apruebas. Luego se envía.",
        "lede": "Sube un PDF, mejora con IA local y adapta a cada oferta sin inventar experiencia.",
        "cta": "Subir mi CV",
        "path": "/login",
        "hooks": [
            "Gratis: 1 currículum y 3 mejoras. Profesional: 10 CVs y 100 mejoras.",
            "Tu CV no tiene que viajar a un SaaS de Estados Unidos.",
        ],
    },
    {
        "slug": "lanzamiento",
        "kicker": "Early access",
        "title": "JobsWorld sale: copiloto de empleo a precio de volumen",
        "lede": "Feed España + internacional, verdad de la oferta y planes desde 0 €. Profesional 14,90 €.",
        "cta": "Reservar plaza",
        "path": "/",
        "hooks": [
            "Mitad de precio que Teal/Huntr, con portales ES de verdad.",
            "Invita a un amigo cuando tengas cuenta: tu enlace de referral.",
        ],
    },
]


def _social_path() -> Path:
    p = get_settings().data_dir / "social.json"
    if not p.exists():
        p.write_text(json.dumps(_default_social(), ensure_ascii=False, indent=2), encoding="utf-8")
    return p


def _default_social() -> dict[str, Any]:
    s = get_settings()
    return {
        "public_base_url": (getattr(s, "public_base_url", None) or "").rstrip("/") or "",
        "linkedin_org_url": "",
        "linkedin_personal_url": "",
        "x_handle": "",
        "instagram_handle": "",
        "facebook_page_url": "",
        "telegram_channel": "",
        "telegram_chat_id": s.telegram_chat_id if hasattr(s, "telegram_chat_id") else "",
        "whatsapp_business": "",
        "reddit_sub": "spain",
        "notes": "Rellena handles. Ver docs/ADMIN-LANZAMIENTO.md",
        "updated_at": None,
    }


def get_social() -> dict[str, Any]:
    try:
        data = json.loads(_social_path().read_text(encoding="utf-8"))
        base = _default_social()
        if isinstance(data, dict):
            base.update({k: v for k, v in data.items() if v is not None})
        return base
    except Exception:  # noqa: BLE001
        return _default_social()


def save_social(payload: dict[str, Any]) -> dict[str, Any]:
    current = get_social()
    allowed = set(_default_social().keys())
    for k, v in payload.items():
        if k in allowed and k != "updated_at":
            current[k] = v.strip() if isinstance(v, str) else v
    current["updated_at"] = datetime.now(timezone.utc).isoformat()
    _social_path().write_text(json.dumps(current, ensure_ascii=False, indent=2), encoding="utf-8")
    return current


def landing_by_slug(slug: str) -> dict[str, Any] | None:
    for item in LANDINGS:
        if item["slug"] == slug:
            return dict(item)
    return None


def qr_image(url: str, size: int = 280) -> str:
    return f"https://api.qrserver.com/v1/create-qr-code/?size={size}x{size}&data={quote(url, safe='')}"


def landing_copies(item: dict[str, Any], url: str) -> dict[str, str]:
    hook = (item.get("hooks") or [""])[0]
    title = item.get("title") or "JobsWorld"
    lede = item.get("lede") or ""
    return {
        "linkedin": f"{title}\n\n{lede}\n\n{hook}\n\nEmpieza gratis → {url}",
        "x": f"{title[:200]}\n{url}"[:280],
        "telegram": f"{item.get('kicker')}: {lede}\n{url}",
        "whatsapp": f"{title}\n{lede}\n{url}",
        "threads": f"{title}\n{lede}\n{url}",
        "reddit": title[:280],
    }


def list_landings(origin: str = "") -> list[dict[str, Any]]:
    origin = (origin or "").rstrip("/")
    out = []
    for item in LANDINGS:
        row = dict(item)
        url = f"{origin}/l/{item['slug']}" if origin else f"/l/{item['slug']}"
        row["url"] = url
        row["qr"] = qr_image(url)
        row["copies"] = landing_copies(row, url)
        out.append(row)
    return out


def compose_urls(*, channel: str, text: str, url: str) -> dict[str, str]:
    enc_url = quote(url, safe="")
    enc_text = quote(text[:500], safe="")
    enc_both = quote(f"{text[:280]} {url}".strip(), safe="")
    channel = (channel or "linkedin").lower()
    urls = {
        "linkedin": f"https://www.linkedin.com/sharing/share-offsite/?url={enc_url}",
        "x": f"https://twitter.com/intent/tweet?text={enc_text}&url={enc_url}",
        "telegram": f"https://t.me/share/url?url={enc_url}&text={enc_text}",
        "whatsapp": f"https://wa.me/?text={enc_both}",
        "facebook": f"https://www.facebook.com/sharer/sharer.php?u={enc_url}",
        "threads": f"https://www.threads.net/intent/post?text={enc_both}",
        "reddit": f"https://www.reddit.com/submit?url={enc_url}&title={enc_text}",
        "email": f"mailto:?subject={quote('JobsWorld')}&body={enc_both}",
    }
    urls["primary"] = urls.get(channel, urls["linkedin"])
    return urls


def utm_link(origin: str, path: str, *, source: str, campaign: str, medium: str = "social") -> str:
    origin = origin.rstrip("/")
    path = path if path.startswith("/") else f"/{path}"
    q = urlencode({"utm_source": source, "utm_medium": medium, "utm_campaign": campaign})
    sep = "&" if "?" in path else "?"
    return f"{origin}{path}{sep}{q}"


def enrich_campaign(item: dict[str, Any], origin: str) -> dict[str, Any]:
    channel = item.get("channel") or "linkedin"
    camp = item.get("utm_campaign") or "launch"
    landing = "/l/lanzamiento"
    if "fantasma" in (item.get("title") or "").lower() or "ghost" in camp:
        landing = "/l/ofertas-fantasma"
    elif "cv" in (item.get("title") or "").lower():
        landing = "/l/cv-ia"
    elif "valencia" in (item.get("title") or "").lower():
        landing = "/l/valencia"
    elif "remoto" in (item.get("title") or "").lower():
        landing = "/l/remoto-espana"
    share = utm_link(origin, landing, source=channel, campaign=camp)
    body = item.get("body") or ""
    if share not in body:
        body_share = f"{body.rstrip()}\n{share}"
    else:
        body_share = body
    return {
        **item,
        "share_url": share,
        "compose": compose_urls(channel=channel, text=body, url=share),
        "body_with_link": body_share,
        "howto": _howto(channel),
    }


def _howto(channel: str) -> str:
    return {
        "linkedin": "Copiar texto → Abrir composer (comparte la URL) → pegar el copy en el post → Publicar → Marcar enviada.",
        "x": "Abrir Intent de X (texto+URL ya van) → Publicar → Marcar enviada.",
        "telegram": "Si hay bot+chat, usa «Enviar por bot». Si no: Abrir share de Telegram → canal → Marcar enviada.",
        "whatsapp": "Abre WhatsApp con el texto listo (estados o grupos de empleo).",
        "facebook": "El sharer solo lleva URL. Pega el copy a mano en la Page.",
        "threads": "Intent de Threads lleva texto+URL. Publica y marca enviada.",
        "reddit": "Revisa reglas del sub. Título = hook. URL = landing UTM.",
        "email": "Abre el cliente de correo con asunto y cuerpo listos.",
    }.get(channel, "Copia el texto, abre el composer, publica, marca enviada.")


def kit_markdown(origin: str, campaigns: list[dict[str, Any]], social: dict[str, Any]) -> str:
    lines = [
        "# Kit de lanzamiento JobsWorld",
        "",
        f"Generado: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}",
        f"Origen: {origin}",
        "",
        "## Landings",
        "",
    ]
    for L in list_landings(origin):
        lines.append(f"- **{L['kicker']}** `{L['url']}` — {L['title']}")
    lines += ["", "## Redes configuradas", ""]
    for k, v in social.items():
        if k in {"notes", "updated_at"}:
            continue
        lines.append(f"- `{k}`: {v or '(vacío)'}")
    lines += ["", "## Cola de campañas", ""]
    for c in campaigns:
        en = enrich_campaign(c, origin)
        lines += [
            f"### {en.get('title')}",
            f"- Canal: {en.get('channel')} · estado: {en.get('status')} · UTM: {en.get('utm_campaign')}",
            f"- URL: {en.get('share_url')}",
            f"- Composer: {en.get('compose', {}).get('primary')}",
            f"- Cómo: {en.get('howto')}",
            "",
            "```",
            en.get("body_with_link") or "",
            "```",
            "",
        ]
    lines += [
        "## Manual",
        "Ver `docs/ADMIN-LANZAMIENTO.md` (LinkedIn org, Meta, X, Telegram bot, Ads, OG).",
        f"- Link-in-bio: {origin}/go",
        "- Descargas admin: kit.md, ads.csv, week.ics",
        "",
    ]
    return "\n".join(lines)


def og_html(slug: str, origin: str) -> str | None:
    item = landing_by_slug(slug)
    if not item:
        return None
    origin = origin.rstrip("/")
    url = f"{origin}/l/{slug}"
    title = f"{item['title']} · JobsWorld"
    desc = item["lede"]
    return f"""<!doctype html>
<html lang="es"><head>
<meta charset="utf-8"/>
<title>{title}</title>
<meta name="description" content="{desc}"/>
<meta property="og:title" content="{title}"/>
<meta property="og:description" content="{desc}"/>
<meta property="og:url" content="{url}"/>
<meta property="og:type" content="website"/>
<meta property="og:image" content="{origin}/icon-512.png"/>
<meta name="twitter:card" content="summary"/>
<meta http-equiv="refresh" content="0;url={url}"/>
<link rel="canonical" href="{url}"/>
</head><body>
<p>{desc}</p>
<p><a href="{url}">Continuar</a></p>
</body></html>"""


def send_telegram(text: str, chat_id: str | None = None) -> dict[str, Any]:
    settings = get_settings()
    token = (settings.telegram_bot_token or "").strip()
    if not token:
        return {
            "ok": False,
            "mode": "manual",
            "detail": "TELEGRAM_BOT_TOKEN no configurado. Usa composer Telegram o el manual.",
        }
    social = get_social()
    dest = (chat_id or social.get("telegram_chat_id") or "").strip()
    if not dest:
        return {
            "ok": False,
            "mode": "manual",
            "detail": "Falta telegram_chat_id (social.json o campo en Lanzamiento). El bot debe haber recibido /start.",
        }
    try:
        import httpx

        resp = httpx.post(
            f"https://api.telegram.org/bot{token}/sendMessage",
            json={"chat_id": dest, "text": text[:3900], "disable_web_page_preview": False},
            timeout=20.0,
        )
        data = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {}
        ok = bool(data.get("ok")) if isinstance(data, dict) else resp.is_success
        return {"ok": ok, "mode": "bot", "status": resp.status_code, "detail": data if not ok else "Enviado"}
    except Exception as exc:  # noqa: BLE001
        return {"ok": False, "mode": "bot", "detail": str(exc)}


def public_bio(origin: str, social: dict[str, Any] | None = None) -> dict[str, Any]:
    social = social or get_social()
    origin = (origin or "").rstrip("/")
    links: list[dict[str, str]] = []
    mapping = [
        ("linkedin_org_url", "LinkedIn empresa"),
        ("linkedin_personal_url", "LinkedIn founder"),
        ("facebook_page_url", "Facebook"),
        ("whatsapp_business", "WhatsApp"),
    ]
    for key, label in mapping:
        val = (social.get(key) or "").strip()
        if val:
            links.append({"id": key, "label": label, "url": val})
    handle_x = (social.get("x_handle") or "").lstrip("@")
    if handle_x:
        links.append({"id": "x", "label": "X", "url": f"https://x.com/{handle_x}"})
    ig = (social.get("instagram_handle") or "").lstrip("@")
    if ig:
        links.append({"id": "instagram", "label": "Instagram", "url": f"https://instagram.com/{ig}"})
    tg = (social.get("telegram_channel") or "").strip()
    if tg:
        url = tg if tg.startswith("http") else f"https://t.me/{tg.lstrip('@')}"
        links.append({"id": "telegram", "label": "Telegram", "url": url})
    reddit = (social.get("reddit_sub") or "").lstrip("/")
    if reddit:
        links.append({"id": "reddit", "label": "Reddit", "url": f"https://www.reddit.com/r/{reddit.replace('r/', '')}"})
    landings = [{"label": L["kicker"], "url": L["url"]} for L in list_landings(origin)]
    return {
        "origin": origin,
        "links": links,
        "landings": landings,
        "url": f"{origin}/go" if origin else "/go",
    }


def readiness(
    *,
    origin: str,
    social: dict[str, Any],
    telegram_bot: bool,
    campaigns: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    campaigns = campaigns or []
    origin_ok = bool(origin) and "127.0.0.1" not in origin and "localhost" not in origin
    items = [
        {
            "key": "public_url",
            "ok": origin_ok,
            "label": "URL pública (no localhost)",
            "hint": "Tailscale / dominio en Redes o PUBLIC_BASE_URL. Sin esto, UTM y OG fallan fuera de tu PC.",
        },
        {
            "key": "linkedin",
            "ok": bool(social.get("linkedin_org_url") or social.get("linkedin_personal_url")),
            "label": "LinkedIn",
            "hint": "Page de empresa o perfil founder. Composer nativo; no hay API sin app review.",
        },
        {
            "key": "x",
            "ok": bool(social.get("x_handle")),
            "label": "X / Twitter",
            "hint": "Handle sin @. Intent de tweet rellena texto+URL.",
        },
        {
            "key": "telegram",
            "ok": bool(social.get("telegram_channel") or social.get("telegram_chat_id")),
            "label": "Telegram canal / chat",
            "hint": "Canal público o chat_id numérico tras /start al bot.",
        },
        {
            "key": "telegram_bot",
            "ok": telegram_bot,
            "label": "Bot Telegram (envío real)",
            "hint": "TELEGRAM_BOT_TOKEN en .env. Único canal que publica sin pegar a mano.",
        },
        {
            "key": "instagram_or_facebook",
            "ok": bool(social.get("instagram_handle") or social.get("facebook_page_url")),
            "label": "Instagram o Facebook",
            "hint": "Meta no deja publicar por API a Pages nuevas. Composer + pegar copy.",
        },
        {
            "key": "campaigns",
            "ok": len(campaigns) > 0,
            "label": "Cola de campañas",
            "hint": "Pulsa «Generar semana» o usa las 3 semillas del kit.",
        },
        {
            "key": "sent",
            "ok": any(c.get("status") == "sent" for c in campaigns),
            "label": "Al menos un post marcado enviado",
            "hint": "Tras publicar, pulsa Marcar enviada. Así el kit no miente.",
        },
    ]
    score = round(100 * sum(1 for i in items if i["ok"]) / max(1, len(items)))
    return {"score": score, "items": items, "origin": origin}


def ads_csv(origin: str) -> str:
    """CSV listo para Ads Manager / Editor (columnas humanas, no API)."""
    rows = [
        "Campaign,AdSet,AdName,Headline,PrimaryText,Description,Link,utm_source,utm_medium,utm_campaign"
    ]
    for L in list_landings(origin):
        copies = L.get("copies") or {}
        text = (copies.get("linkedin") or L.get("lede") or "").replace('"', "'").replace("\n", " ")
        rows.append(
            ",".join(
                [
                    '"JobsWorld Launch"',
                    f"\"{L['kicker']}\"",
                    f"\"{L['slug']}\"",
                    f"\"{(L.get('title') or '')[:40]}\"",
                    f"\"{text[:125]}\"",
                    f"\"{(L.get('lede') or '')[:90]}\"",
                    L["url"],
                    "meta",
                    "paid",
                    L["slug"],
                ]
            )
        )
    return "\n".join(rows) + "\n"


def week_ics(campaigns: list[dict[str, Any]], origin: str) -> str:
    from datetime import timedelta

    stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    start_base = datetime.now(timezone.utc).replace(hour=7, minute=30, second=0, microsecond=0)
    lines = [
        "BEGIN:VCALENDAR",
        "VERSION:2.0",
        "PRODID:-//JobsWorld//Launch//ES",
        "CALSCALE:GREGORIAN",
        "X-WR-CALNAME:JobsWorld publicación",
    ]
    for i, c in enumerate(campaigns[:14]):
        en = enrich_campaign(c, origin)
        start = start_base + timedelta(days=i)
        end = start + timedelta(minutes=20)
        desc = (en.get("body_with_link") or "").replace("\\", "\\\\").replace(";", "\\;").replace(",", "\\,")
        desc = desc.replace("\n", "\\n")[:700]
        title = (en.get("title") or "Post")[:70].replace(",", " ")
        lines += [
            "BEGIN:VEVENT",
            f"UID:{en.get('id', i)}@jobsworld.launch",
            f"DTSTAMP:{stamp}",
            f"DTSTART:{start.strftime('%Y%m%dT%H%M%SZ')}",
            f"DTEND:{end.strftime('%Y%m%dT%H%M%SZ')}",
            f"SUMMARY:Publicar {en.get('channel')}: {title}",
            f"DESCRIPTION:{desc}",
            f"URL:{en.get('share_url') or ''}",
            "END:VEVENT",
        ]
    lines.append("END:VCALENDAR")
    return "\r\n".join(lines) + "\r\n"


def is_crawler(user_agent: str) -> bool:
    ua = (user_agent or "").lower()
    needles = (
        "facebookexternalhit",
        "facebot",
        "twitterbot",
        "linkedinbot",
        "slackbot",
        "telegrambot",
        "whatsapp",
        "discordbot",
        "googlebot",
        "bingbot",
        "applebot",
        "embedly",
        "pinterest",
    )
    return any(n in ua for n in needles)
