"""GEO / AEO module — Generative Engine Optimization.

Audits how ready a site is to be *cited by AI systems* (ChatGPT Search,
Claude, Perplexity, Google AI Overviews, Bing Copilot) and generates the
artifacts those systems consume: llms.txt, JSON-LD structured data, and
FAQ schema.
"""
from __future__ import annotations
import json
import re
from bs4 import BeautifulSoup


AI_READINESS_CHECKS = [
    # (key, weight, human description)
    ("json_ld",        3, "JSON-LD structured data present"),
    ("faq_schema",     2, "FAQPage schema (feeds direct AI answers)"),
    ("org_schema",     2, "Organization/LocalBusiness schema (entity identity)"),
    ("article_schema", 1, "Article/Product schema on content pages"),
    ("semantic_html",  2, "Semantic HTML5 (article/section/nav) — chunkable for LLMs"),
    ("question_headings", 2, "Question-style headings (H2/H3 phrased as questions)"),
    ("answer_first",   2, "Answer-first paragraphs (concise summary right after headings)"),
    ("stats_and_facts", 1, "Concrete numbers/dates/stats (LLMs prefer citable specifics)"),
    ("author_signals", 2, "Author/byline & dates visible (E-E-A-T trust signals)"),
    ("llms_txt",       2, "llms.txt file at site root"),
]


def audit_ai_readiness(url: str, html: str, llms_txt_exists: bool,
                       is_homepage: bool = True) -> dict:
    soup = BeautifulSoup(html, "html.parser")
    results, score, max_score = {}, 0, 0

    ld_blocks = soup.find_all("script", type="application/ld+json")
    schemas = []
    for b in ld_blocks:
        try:
            data = json.loads(b.string or "{}")
            items = data if isinstance(data, list) else [data]
            for it in items:
                t = it.get("@type", "")
                schemas.extend(t if isinstance(t, list) else [t])
        except Exception:
            pass

    text = soup.get_text(" ", strip=True)
    headings = [h.get_text(" ", strip=True) for h in soup.find_all(["h2", "h3"])]
    q_headings = [h for h in headings if re.match(
        r"^(how|what|why|when|where|which|who|can|should|is|are|does|do)\b", h.lower())]

    has_org_schema = any(s in schemas for s in
                         ("Organization", "LocalBusiness", "Store",
                          "ProfessionalService", "Restaurant"))
    checks = {
        "json_ld": bool(schemas),
        "faq_schema": "FAQPage" in schemas,
        # Organization/LocalBusiness schema is a site-identity marker that
        # only needs to exist once (the homepage) — auto-fix only ever adds
        # it there, so subpages are never dinged for not repeating it.
        "org_schema": has_org_schema if is_homepage else True,
        "article_schema": any(s in schemas for s in
                              ("Article", "BlogPosting", "Product", "Service", "HowTo")),
        "semantic_html": bool(soup.find(["article", "section", "main"])),
        "question_headings": len(q_headings) >= 2,
        "answer_first": _has_answer_first(soup),
        "stats_and_facts": len(re.findall(r"\b\d[\d,.]*%?\b", text)) >= 10,
        "author_signals": bool(soup.find(attrs={"class": re.compile("author|byline", re.I)})
                               or soup.find("meta", attrs={"name": "author"})
                               or soup.find("time")),
        "llms_txt": llms_txt_exists,
    }

    for key, weight, desc in AI_READINESS_CHECKS:
        ok = checks[key]
        max_score += weight
        if ok:
            score += weight
        results[key] = {"passed": ok, "weight": weight, "description": desc}

    return {
        "url": url,
        "score": round(100 * score / max_score, 1),
        "checks": results,
        "detected_schemas": sorted(set(schemas)),
        "question_headings_found": q_headings[:10],
        "recommendations": _recommendations(checks),
    }


def _has_answer_first(soup) -> bool:
    """Heuristic: does at least one H2 have a <=2 sentence concise paragraph right after?"""
    for h in soup.find_all("h2"):
        p = h.find_next_sibling("p")
        if p:
            sentences = re.split(r"[.!?]+\s", p.get_text(" ", strip=True))
            if 1 <= len([s for s in sentences if s]) <= 3:
                return True
    return False


def _recommendations(checks: dict) -> list[str]:
    rec = []
    if not checks["org_schema"]:
        rec.append("Add Organization/LocalBusiness schema with name, logo, address, sameAs links — this builds your 'entity' in knowledge graphs. [auto-fixable — approve it in your email/dashboard]")
    if not checks["llms_txt"]:
        rec.append("Publish /llms.txt — a curated markdown map of your best content for AI crawlers. [auto-fixable — approve it in your email/dashboard]")
    if not checks["json_ld"]:
        rec.append("Add JSON-LD structured data on this page — the #1 machine-readable signal for AI engines. [needs manual content work — depends what this specific page is about]")
    if not checks["faq_schema"]:
        rec.append("Add a real FAQ section with FAQPage schema; AI assistants lift these Q&As verbatim into answers. [auto-fixable \u2014 if you already have Q&A headings on the page it's picked up automatically, or add real Q&A in Settings \u2192 Business Facts]")
    if not checks["question_headings"]:
        rec.append("Rewrite key H2/H3 headings as natural questions users ask AI ('How much does X cost?'). [needs manual content work \u2014 wording is a judgment call]")
    if not checks["answer_first"]:
        rec.append("Use answer-first writing: a 40–60 word direct answer immediately after each question heading, then detail. [needs manual content work]")
    if not checks["stats_and_facts"]:
        rec.append("Add specific stats, prices, dates and named sources — LLMs preferentially cite concrete, verifiable pages. [auto-fixable once you add a real stat in Settings \u2192 Business Facts]")
    if not checks["author_signals"]:
        rec.append("Show author names, credentials and last-updated dates (E-E-A-T); AI ranking systems weight trust heavily. [auto-fixable once you add a real name in Settings \u2192 Business Facts]")
    return rec


# ------------------------------------------------------------------
# Generators
# ------------------------------------------------------------------

def generate_llms_txt(site: dict, pages: list[dict]) -> str:
    """Generate an llms.txt file per the llmstxt.org convention."""
    lines = [f"# {site['name']}", "",
             f"> {site.get('description', site['name'] + ' — official website.')}", "",
             "## Main pages", ""]
    for p in pages[:15]:
        title = p.get("title") or p["url"]
        lines.append(f"- [{title}]({p['url']}): {p.get('meta_description','')[:120]}")
    lines += ["", "## Contact", "",
              f"- [Website]({site['url']})"]
    return "\n".join(lines)


def generate_org_schema(site: dict) -> str:
    schema = {
        "@context": "https://schema.org",
        "@type": site.get("business_type", "Organization"),
        "name": site["name"],
        "url": site["url"],
        "logo": site["url"].rstrip("/") + "/logo.png",
        "sameAs": [
            "https://www.linkedin.com/company/YOUR-COMPANY",
            "https://www.instagram.com/YOUR-HANDLE",
            "https://maps.google.com/?cid=YOUR-GBP-ID",
        ],
    }
    if site.get("target_locations"):
        schema["areaServed"] = site["target_locations"]
    if site.get("business_type") in ("LocalBusiness", "Store", "Restaurant",
                                     "ProfessionalService"):
        schema["address"] = {
            "@type": "PostalAddress",
            "addressLocality": (site.get("target_locations") or ["CITY"])[0],
            "addressCountry": "IN",
            "streetAddress": "FILL_IN",
        }
        schema["telephone"] = "FILL_IN"
        schema["openingHours"] = "Mo-Sa 09:00-18:00"
    return json.dumps(schema, indent=2)


def generate_faq_schema(faqs: list[tuple[str, str]]) -> str:
    return json.dumps({
        "@context": "https://schema.org",
        "@type": "FAQPage",
        "mainEntity": [
            {"@type": "Question", "name": q,
             "acceptedAnswer": {"@type": "Answer", "text": a}}
            for q, a in faqs
        ],
    }, indent=2)
