# retoor <retoor@molodetz.nl>

from __future__ import annotations

from urllib.parse import urlparse

from .base import (
    CRITICAL,
    HIGH,
    MEDIUM,
    LOW,
    PASS,
    WARN,
    FAIL,
    INFO,
    Check,
    PageContext,
    SiteContext,
    ok_check,
    page_check,
    site_check,
)

CATEGORY = "crawlability"


def _normalize(url: str) -> str:
    return url.rstrip("/").split("#")[0]


@page_check
def http_status(page: PageContext, site: SiteContext) -> Check:
    passed = 200 <= page.status < 300
    return ok_check(
        "crawl.http_status",
        CATEGORY,
        "HTTP status",
        passed,
        severity=CRITICAL,
        value=str(page.status),
        recommendation="Return a 200 OK status for indexable pages.",
        url=page.url,
    )


@page_check
def redirects(page: PageContext, site: SiteContext) -> Check:
    hops = len(page.redirect_chain)
    if hops == 0:
        return Check(
            "crawl.redirects",
            CATEGORY,
            "Redirect chain",
            PASS,
            LOW,
            value="no redirect",
            url=page.url,
        )
    status = WARN if hops <= 2 else FAIL
    return Check(
        "crawl.redirects",
        CATEGORY,
        "Redirect chain",
        status,
        MEDIUM,
        value=f"{hops} hop(s)",
        recommendation="Link directly to the final URL to avoid redirect latency and link-equity loss.",
        details={"chain": page.redirect_chain},
        url=page.url,
    )


@page_check
def https(page: PageContext, site: SiteContext) -> Check:
    passed = page.url.startswith("https://")
    return ok_check(
        "crawl.https",
        CATEGORY,
        "Served over HTTPS",
        passed,
        severity=HIGH,
        value="https" if passed else "http",
        recommendation="Serve every page over HTTPS.",
        url=page.url,
    )


@page_check
def hsts(page: PageContext, site: SiteContext) -> Check:
    if not page.url.startswith("https://"):
        return None
    present = bool(page.headers.get("strict-transport-security"))
    return ok_check(
        "crawl.hsts",
        CATEGORY,
        "HSTS header",
        present,
        severity=LOW,
        value="present" if present else "missing",
        recommendation="Add a Strict-Transport-Security header to enforce HTTPS.",
        warn=True,
        url=page.url,
    )


@page_check
def canonical(page: PageContext, site: SiteContext) -> list:
    href = page.dom.get("canonical", "")
    count = int(page.dom.get("canonicalCount", 0) or 0)
    results = [
        ok_check(
            "crawl.canonical_present",
            CATEGORY,
            "Canonical tag",
            bool(href),
            severity=MEDIUM,
            value=href or "missing",
            recommendation="Add a self-referential <link rel=canonical> to consolidate duplicates.",
            warn=True,
            url=page.url,
        )
    ]
    if count > 1:
        results.append(
            Check(
                "crawl.canonical_single",
                CATEGORY,
                "Single canonical",
                FAIL,
                MEDIUM,
                value=f"{count} canonicals",
                recommendation="Declare exactly one canonical URL per page.",
                url=page.url,
            )
        )
    if href:
        same = _normalize(href) == _normalize(page.url)
        results.append(
            ok_check(
                "crawl.canonical_self",
                CATEGORY,
                "Self-referential canonical",
                same,
                severity=LOW,
                value="self" if same else href,
                recommendation="Point the canonical at this page's own URL unless intentionally consolidating.",
                warn=True,
                url=page.url,
            )
        )
    return results


@page_check
def indexability(page: PageContext, site: SiteContext) -> Check:
    robots = str(page.dom.get("metaRobots", "")).lower()
    header = str(page.headers.get("x-robots-tag", "")).lower()
    blocked = "noindex" in robots or "noindex" in header
    return ok_check(
        "crawl.indexable",
        CATEGORY,
        "Indexable (no noindex)",
        not blocked,
        severity=HIGH,
        value="noindex" if blocked else "indexable",
        recommendation="Remove noindex from meta robots / X-Robots-Tag if this page should rank.",
        warn=True,
        url=page.url,
    )


@page_check
def url_hygiene(page: PageContext, site: SiteContext) -> Check:
    parsed = urlparse(page.url)
    path = parsed.path
    issues = []
    if len(page.url) > 100:
        issues.append("long")
    if any(c.isupper() for c in path):
        issues.append("uppercase")
    if "_" in path:
        issues.append("underscores")
    if parsed.query.count("&") >= 3:
        issues.append("many params")
    passed = not issues
    return ok_check(
        "crawl.url_hygiene",
        CATEGORY,
        "Clean URL",
        passed,
        severity=LOW,
        value="clean" if passed else ", ".join(issues),
        recommendation="Use short lowercase hyphenated paths with few query parameters.",
        warn=True,
        url=page.url,
    )


@page_check
def mixed_content(page: PageContext, site: SiteContext) -> Check:
    if not page.url.startswith("https://"):
        return None
    items = page.dom.get("mixedContent", []) or []
    passed = not items
    return ok_check(
        "crawl.mixed_content",
        CATEGORY,
        "No mixed content",
        passed,
        severity=HIGH,
        value="clean" if passed else f"{len(items)} insecure resource(s)",
        recommendation="Load every sub-resource over HTTPS to avoid mixed-content blocking.",
        details={"resources": items[:20]},
        url=page.url,
    )


@site_check
def robots_txt(site: SiteContext) -> list:
    robots = site.robots or {}
    fetched = robots.get("status") == 200
    results = [
        ok_check(
            "crawl.robots_txt",
            CATEGORY,
            "robots.txt present",
            fetched,
            severity=MEDIUM,
            value=f"{robots.get('status', 'n/a')}",
            recommendation="Publish a robots.txt at the site root.",
            warn=True,
        )
    ]
    if fetched:
        results.append(
            ok_check(
                "crawl.robots_sitemap",
                CATEGORY,
                "Sitemap declared in robots.txt",
                bool(robots.get("sitemap_urls")),
                severity=LOW,
                value=", ".join(robots.get("sitemap_urls", [])[:3]) or "none",
                recommendation="Reference your sitemap with a Sitemap: directive in robots.txt.",
                warn=True,
            )
        )
        if robots.get("blocks_target"):
            results.append(
                Check(
                    "crawl.robots_blocks_target",
                    CATEGORY,
                    "robots.txt blocks the audited URL",
                    FAIL,
                    HIGH,
                    value="disallowed",
                    recommendation="The audited URL is disallowed in robots.txt; allow it if it should be crawled.",
                )
            )
    return results


@site_check
def sitemap_xml(site: SiteContext) -> list:
    sm = site.sitemap or {}
    fetched = sm.get("status") == 200
    results = [
        ok_check(
            "crawl.sitemap_present",
            CATEGORY,
            "XML sitemap reachable",
            fetched,
            severity=MEDIUM,
            value=f"{sm.get('url_count', 0)} url(s)" if fetched else f"{sm.get('status', 'n/a')}",
            recommendation="Publish an XML sitemap and submit it to search engines.",
            warn=True,
        )
    ]
    if fetched:
        results.append(
            ok_check(
                "crawl.sitemap_valid",
                CATEGORY,
                "Sitemap is valid XML",
                bool(sm.get("valid_xml")),
                severity=MEDIUM,
                value="valid" if sm.get("valid_xml") else "invalid",
                recommendation="Fix the sitemap XML so crawlers can parse it.",
            )
        )
        results.append(
            ok_check(
                "crawl.sitemap_lastmod",
                CATEGORY,
                "Sitemap uses lastmod",
                int(sm.get("lastmod_count", 0) or 0) > 0,
                severity=LOW,
                value=f"{sm.get('lastmod_count', 0)} entries",
                recommendation="Add <lastmod> dates so crawlers prioritise fresh pages.",
                warn=True,
            )
        )
    return results
