"""AI Citation Tracker — measures whether AI engines mention/cite your brand.

For each configured customer question, asks the enabled engines (OpenAI,
Anthropic, Perplexity — the ones behind ChatGPT, Claude and Perplexity
answers) and records:
  - mentioned: is your brand named in the answer?
  - cited:     is your domain linked/cited?
  - which competitors were named instead

Only engines with an API key in config are queried. Keys never leave your
machine except to the respective official API. Because AI answers vary
run to run, rates are meaningful over many checks, not a single one.
"""
from __future__ import annotations
import json
import re
import urllib.parse

import requests

from .db import DB

TIMEOUT = 60


class EngineError(Exception):
    """Carries the API's own explanation of what went wrong."""


def _check(r: requests.Response):
    if r.status_code >= 400:
        try:
            detail = r.json().get("error", {}).get("message", r.text[:200])
        except Exception:
            detail = r.text[:200]
        raise EngineError(f"HTTP {r.status_code}: {detail}")


# ----------------------------------------------------------------------
# Engine adapters — each returns (answer_text, cited_urls)
# ----------------------------------------------------------------------

def _ask_openai(question: str, key: str, model: str = "gpt-4o-mini"):
    r = requests.post(
        "https://api.openai.com/v1/chat/completions",
        headers={"Authorization": f"Bearer {key}"},
        json={"model": model,
              "messages": [{"role": "user", "content": question}],
              "max_tokens": 500},
        timeout=TIMEOUT)
    _check(r)
    text = r.json()["choices"][0]["message"]["content"]
    return text, _urls_in(text)


def _ask_anthropic(question: str, key: str, model: str = "claude-sonnet-4-6"):
    r = requests.post(
        "https://api.anthropic.com/v1/messages",
        headers={"x-api-key": key, "anthropic-version": "2023-06-01"},
        json={"model": model, "max_tokens": 500,
              "messages": [{"role": "user", "content": question}]},
        timeout=TIMEOUT)
    _check(r)
    text = "".join(b.get("text", "") for b in r.json().get("content", []))
    return text, _urls_in(text)


def _ask_perplexity(question: str, key: str, model: str = "sonar"):
    r = requests.post(
        "https://api.perplexity.ai/chat/completions",
        headers={"Authorization": f"Bearer {key}"},
        json={"model": model,
              "messages": [{"role": "user", "content": question}]},
        timeout=TIMEOUT)
    _check(r)
    data = r.json()
    text = data["choices"][0]["message"]["content"]
    cited = list(data.get("citations", [])) + _urls_in(text)
    return text, cited


ENGINES = {
    "openai": _ask_openai,
    "anthropic": _ask_anthropic,
    "perplexity": _ask_perplexity,
}


def _urls_in(text: str) -> list[str]:
    return re.findall(r"https?://[^\s)\]>\"']+", text)


# ----------------------------------------------------------------------
# Answer analysis
# ----------------------------------------------------------------------

def analyze_answer(answer: str, cited_urls: list[str], site: dict) -> dict:
    """Pure function (unit-testable, no network): detect brand mention,
    domain citation, and competitor mentions in an engine's answer."""
    low = answer.lower()
    brand = site["name"].lower()
    domain = urllib.parse.urlparse(site["url"]).netloc.lower().removeprefix("www.")

    mentioned = brand in low or domain in low
    cited = any(domain in u.lower() for u in cited_urls)

    competitors = []
    squashed = re.sub(r"[\s\-_]", "", low)  # "third wave coffee" -> "thirdwavecoffee"
    for comp in site.get("competitors", []):
        cdom = urllib.parse.urlparse(comp).netloc.lower().removeprefix("www.")
        cname = cdom.split(".")[0]
        if cdom in low or (len(cname) > 3 and cname in squashed) \
           or any(cdom in u.lower() for u in cited_urls):
            competitors.append(cdom)

    return {"mentioned": mentioned, "cited": cited,
            "competitors": competitors,
            "snippet": answer.strip()[:400]}


# ----------------------------------------------------------------------
# Runner
# ----------------------------------------------------------------------

def run_citation_checks(db: DB, site: dict, cit_cfg: dict,
                        log=print) -> dict:
    questions = cit_cfg.get("questions") or _default_questions(site)
    keys = cit_cfg.get("api_keys", {})
    enabled = [e for e in ENGINES if keys.get(e)]
    if not enabled:
        log("AI citation tracker: skipped (optional, off by default). This "
           "is separate from your AI readiness score above — readiness is "
           "free and already being checked. Citation tracking would call "
           "OpenAI/Anthropic/Perplexity's own paid APIs to see if they "
           "mention your business by name; add a key in Settings only if "
           "you want that specific feature.")
        return {"ran": 0, "engines": []}

    ran = 0
    for q in questions[: cit_cfg.get("max_questions_per_run", 5)]:
        for engine in enabled:
            try:
                answer, cited_urls = ENGINES[engine](q, keys[engine])
                res = analyze_answer(answer, cited_urls, site)
                db.record_citation(site["name"], engine, q,
                                   res["mentioned"], res["cited"],
                                   res["competitors"], res["snippet"])
                ran += 1
                log(f"[{engine}] '{q[:50]}…' mentioned={res['mentioned']} "
                    f"cited={res['cited']} competitors={res['competitors']}")
            except Exception as e:
                log(f"[{engine}] check failed: {e}")
    return {"ran": ran, "engines": enabled}


def _default_questions(site: dict) -> list[str]:
    """Build customer-style questions from keywords + locations."""
    qs = []
    locs = site.get("target_locations", [])
    loc = f" in {locs[0]}" if locs else ""
    for kw in site.get("target_keywords", [])[:5]:
        k = kw.lower()
        if k.startswith(("best", "top")):
            qs.append(f"What is the {k}{'' if loc.lower() in k else loc}? "
                      f"Please recommend specific businesses.")
        else:
            qs.append(f"Which businesses would you recommend for {k}{loc}?")
    return qs or [f"Tell me about {site['name']} and whether you would recommend it."]
