"""Prueba de intercambio entre las páginas de ciudad de una vertical.

Baja las páginas /<vertical>-<ciudad> que lista el sitemap, se queda con el texto
que cambia de página a página (el hero, el <main> y las FAQs; sin menú, reseñas,
CTA ni footer), reemplaza el nombre de la ciudad por CITY y compara cada par de
ciudades palabra por palabra. Una similitud alta significa que el texto sirve
para cualquier ciudad con solo cambiarle el nombre: el patrón de página puerta.

Da la similitud media, mínima y máxima, los pares más parecidos y la similitud
por sección (por el id de cada bloque), para saber qué sección pesa más. Se usa
igual antes y después de un cambio, así los números de resultado.md son
comparables.

Uso (solo librería estándar; sirve el venv de claude-seo):
  .claude/skills/seo/.venv/bin/python -I seo-optimizations/_scripts/similitud-ciudades.py \
      https://www.patiospoolsanddriveways.com paver-driveways [salida.json]
"""

import difflib
import itertools
import json
import re
import ssl
import statistics
import sys
import urllib.request
from html.parser import HTMLParser

# La base de cada vertical termina así; todo lo demás /<vertical>-* es una ciudad.
BASE_SUFFIX = "palm-beach-broward"
# Bloques que se comparan: los ids de fuera de <main> que también cambian por ciudad.
ROOT_IDS = {"hero", "faq"}
VOID_TAGS = {"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"}
SKIP_TAGS = {"script", "style", "svg", "noscript", "template"}

# Un .test local de lerd no tiene certificado público.
CONTEXT = ssl.create_default_context()
CONTEXT.check_hostname = False
CONTEXT.verify_mode = ssl.CERT_NONE


def fetch(url: str) -> str:
    request = urllib.request.Request(url, headers={"User-Agent": "similitud-ciudades"})
    with urllib.request.urlopen(request, timeout=30, context=CONTEXT) as response:
        return response.read().decode("utf-8", errors="ignore")


class SectionText(HTMLParser):
    """Junta el texto visible por sección: la primera etiqueta con id dentro de
    <main>, o el propio bloque cuando es #hero o #faq."""

    def __init__(self) -> None:
        super().__init__(convert_charrefs=True)
        self.stack: list[tuple[str, str | None]] = []
        self.sections: dict[str, list[str]] = {}

    def handle_starttag(self, tag, attrs):
        if tag in VOID_TAGS:
            return
        self.stack.append((tag, dict(attrs).get("id")))

    def handle_endtag(self, tag):
        for i in range(len(self.stack) - 1, -1, -1):
            if self.stack[i][0] == tag:
                del self.stack[i:]
                return

    def section(self) -> str | None:
        if any(tag in SKIP_TAGS for tag, _ in self.stack):
            return None
        for depth, (tag, element_id) in enumerate(self.stack):
            if element_id in ROOT_IDS:
                return element_id
            if tag == "main":
                inner = [eid for _, eid in self.stack[depth + 1 :] if eid]
                return inner[0] if inner else "(sin id)"
        return None

    def handle_data(self, data):
        key = self.section()
        if key and data.strip():
            self.sections.setdefault(key, []).append(data)


def city_name(slug: str) -> str:
    return " ".join(word.capitalize() for word in slug.split("-"))


def page_sections(html: str, city_slug: str) -> dict[str, list[str]]:
    parser = SectionText()
    parser.feed(html)
    city = re.compile(re.escape(city_name(city_slug)), re.I)
    return {key: city.sub("CITY", " ".join(chunks)).split() for key, chunks in parser.sections.items()}


def similarity(a: list[str], b: list[str]) -> float:
    if not a and not b:
        return 1.0
    return difflib.SequenceMatcher(None, a, b, autojunk=False).ratio()


def main() -> None:
    if len(sys.argv) < 3:
        sys.exit(__doc__)
    site, vertical = sys.argv[1].rstrip("/"), sys.argv[2]
    output = sys.argv[3] if len(sys.argv) > 3 else None

    sitemap = fetch(f"{site}/sitemap.xml")
    prefix = f"/{vertical}-"
    slugs = sorted(
        {
            path[len(prefix) :]
            for path in re.findall(r"<loc>https?://[^/<]+(/[^<]*)</loc>", sitemap)
            if path.startswith(prefix) and path != f"{prefix}{BASE_SUFFIX}"
        }
    )
    if len(slugs) < 2:
        sys.exit(f"El sitemap de {site} tiene {len(slugs)} páginas de ciudad para {vertical}.")

    pages = {slug: page_sections(fetch(f"{site}{prefix}{slug}"), slug) for slug in slugs}
    words = {slug: [w for key in sections for w in sections[key]] for slug, sections in pages.items()}

    pairs = [(similarity(words[a], words[b]), a, b) for a, b in itertools.combinations(slugs, 2)]
    values = [value for value, _, _ in pairs]

    keys = sorted({key for sections in pages.values() for key in sections})
    by_section = {}
    for key in keys:
        texts = [pages[slug].get(key, []) for slug in slugs]
        by_section[key] = {
            "similitud": round(statistics.mean(similarity(a, b) for a, b in itertools.combinations(texts, 2)), 3),
            "palabras": round(statistics.mean(len(t) for t in texts)),
            "paginas": sum(1 for t in texts if t),
        }

    result = {
        "sitio": site,
        "vertical": vertical,
        "ciudades": len(slugs),
        "pares": len(pairs),
        "similitud_media": round(statistics.mean(values), 3),
        "similitud_min": round(min(values), 3),
        "similitud_max": round(max(values), 3),
        "palabras_por_pagina": round(statistics.mean(len(w) for w in words.values())),
        "pares_mas_parecidos": [
            {"similitud": round(value, 3), "a": a, "b": b} for value, a, b in sorted(pairs, reverse=True)[:3]
        ],
        "secciones": dict(sorted(by_section.items(), key=lambda item: -item[1]["similitud"])),
    }

    print(f"{vertical}: {len(slugs)} ciudades, {len(pairs)} pares, {result['palabras_por_pagina']} palabras por página")
    print(
        f"similitud media {result['similitud_media']:.0%} "
        f"(mín. {result['similitud_min']:.0%}, máx. {result['similitud_max']:.0%})"
    )
    for pair in result["pares_mas_parecidos"]:
        print(f"  {pair['similitud']:.0%}  {pair['a']} / {pair['b']}")
    print("por sección:")
    for key, data in result["secciones"].items():
        print(f"  {key:22} {data['similitud']:4.0%}  {data['palabras']:5} palabras  en {data['paginas']} páginas")

    if output:
        with open(output, "w", encoding="utf-8") as file:
            json.dump(result, file, ensure_ascii=False, indent=2)
            file.write("\n")


if __name__ == "__main__":
    main()
