# retoor <retoor@molodetz.nl>

from __future__ import annotations

from .base import (
    HIGH,
    MEDIUM,
    LOW,
    INFO,
    Check,
    PageContext,
    SiteContext,
    ok_check,
    page_check,
)

CATEGORY = "performance"

LCP_GOOD_MS = 2500
LCP_POOR_MS = 4000
CLS_GOOD = 0.1
CLS_POOR = 0.25
FCP_GOOD_MS = 1800
TTFB_GOOD_MS = 800
PAGE_WEIGHT_BUDGET = 3_000_000
REQUEST_BUDGET = 80
DOM_NODE_BUDGET = 1500
MODERN_FORMATS = (".webp", ".avif")


def _ms(metrics: dict, key: str) -> float:
    try:
        return float(metrics.get(key) or 0)
    except (TypeError, ValueError):
        return 0.0


@page_check
def lcp(page: PageContext, site: SiteContext) -> Check:
    value = _ms(page.metrics, "lcp")
    if value <= 0:
        return None
    good = value <= LCP_GOOD_MS
    return ok_check(
        "perf.lcp",
        CATEGORY,
        "Largest Contentful Paint",
        good,
        severity=HIGH,
        value=f"{value / 1000:.2f}s",
        recommendation=f"Reduce LCP below {LCP_GOOD_MS / 1000:.1f}s (optimise hero image/render path).",
        warn=value <= LCP_POOR_MS,
        url=page.url,
    )


@page_check
def cls(page: PageContext, site: SiteContext) -> Check:
    value = _ms(page.metrics, "cls")
    good = value <= CLS_GOOD
    return ok_check(
        "perf.cls",
        CATEGORY,
        "Cumulative Layout Shift",
        good,
        severity=HIGH,
        value=f"{value:.3f}",
        recommendation=f"Keep CLS under {CLS_GOOD} by reserving space for media and ads.",
        warn=value <= CLS_POOR,
        url=page.url,
    )


@page_check
def fcp(page: PageContext, site: SiteContext) -> Check:
    value = _ms(page.metrics, "fcp")
    if value <= 0:
        return None
    return ok_check(
        "perf.fcp",
        CATEGORY,
        "First Contentful Paint",
        value <= FCP_GOOD_MS,
        severity=LOW,
        value=f"{value / 1000:.2f}s",
        recommendation=f"Reduce FCP below {FCP_GOOD_MS / 1000:.1f}s.",
        warn=True,
        url=page.url,
    )


@page_check
def ttfb(page: PageContext, site: SiteContext) -> Check:
    value = _ms(page.metrics, "ttfb")
    if value <= 0:
        return None
    return ok_check(
        "perf.ttfb",
        CATEGORY,
        "Time To First Byte",
        value <= TTFB_GOOD_MS,
        severity=MEDIUM,
        value=f"{value:.0f}ms",
        recommendation=f"Reduce server response time below {TTFB_GOOD_MS}ms.",
        warn=True,
        url=page.url,
    )


@page_check
def page_weight(page: PageContext, site: SiteContext) -> Check:
    total = int(page.metrics.get("transferSize") or 0)
    if total <= 0:
        return None
    return ok_check(
        "perf.page_weight",
        CATEGORY,
        "Total page weight",
        total <= PAGE_WEIGHT_BUDGET,
        severity=MEDIUM,
        value=f"{total / 1_000_000:.2f} MB",
        recommendation=f"Trim total transfer below {PAGE_WEIGHT_BUDGET / 1_000_000:.0f} MB.",
        warn=True,
        url=page.url,
    )


@page_check
def request_count(page: PageContext, site: SiteContext) -> Check:
    count = int(page.metrics.get("resourceCount") or 0)
    if count <= 0:
        return None
    return ok_check(
        "perf.requests",
        CATEGORY,
        "Request count",
        count <= REQUEST_BUDGET,
        severity=LOW,
        value=str(count),
        recommendation=f"Reduce HTTP requests below {REQUEST_BUDGET} (bundle, sprite, lazy-load).",
        warn=True,
        url=page.url,
    )


@page_check
def dom_size(page: PageContext, site: SiteContext) -> Check:
    nodes = int(page.metrics.get("domNodes") or 0)
    if nodes <= 0:
        return None
    return ok_check(
        "perf.dom_size",
        CATEGORY,
        "DOM size",
        nodes <= DOM_NODE_BUDGET,
        severity=LOW,
        value=f"{nodes} nodes",
        recommendation=f"Keep the DOM under {DOM_NODE_BUDGET} nodes for faster rendering.",
        warn=True,
        url=page.url,
    )


@page_check
def compression(page: PageContext, site: SiteContext) -> Check:
    encoding = str(page.headers.get("content-encoding", "")).lower()
    passed = any(token in encoding for token in ("gzip", "br", "zstd", "deflate"))
    return ok_check(
        "perf.compression",
        CATEGORY,
        "Text compression",
        passed,
        severity=MEDIUM,
        value=encoding or "none",
        recommendation="Enable gzip or brotli compression on text responses.",
        warn=True,
        url=page.url,
    )


@page_check
def caching(page: PageContext, site: SiteContext) -> Check:
    cache = str(page.headers.get("cache-control", "")).lower()
    passed = bool(cache) and "no-store" not in cache
    return ok_check(
        "perf.caching",
        CATEGORY,
        "Cache headers",
        passed,
        severity=LOW,
        value=cache or "none",
        recommendation="Set Cache-Control with sensible max-age for static assets.",
        warn=True,
        url=page.url,
    )


@page_check
def http_version(page: PageContext, site: SiteContext) -> Check:
    version = str(page.metrics.get("protocol", "") or "")
    if not version:
        return None
    modern = any(token in version.lower() for token in ("h2", "h3", "http/2", "http/3"))
    return ok_check(
        "perf.http_version",
        CATEGORY,
        "Modern HTTP protocol",
        modern,
        severity=LOW,
        value=version,
        recommendation="Serve over HTTP/2 or HTTP/3 for multiplexed delivery.",
        warn=True,
        url=page.url,
    )


@page_check
def console_errors(page: PageContext, site: SiteContext) -> Check:
    errors = int(page.metrics.get("consoleErrors") or 0)
    return ok_check(
        "perf.console_errors",
        CATEGORY,
        "No console errors",
        errors == 0,
        severity=LOW,
        value="clean" if errors == 0 else f"{errors} error(s)",
        recommendation="Resolve JavaScript console errors emitted on load.",
        warn=True,
        details={"samples": page.metrics.get("consoleErrorSamples", [])[:5]},
        url=page.url,
    )


@page_check
def image_optimization(page: PageContext, site: SiteContext) -> list:
    images = page.dom.get("images", []) or []
    if not images:
        return []
    no_dimensions = [
        img for img in images if not img.get("width") or not img.get("height")
    ]
    legacy_format = [
        img
        for img in images
        if (img.get("src", "") or "").lower().rsplit("?", 1)[0].endswith((".jpg", ".jpeg", ".png"))
    ]
    not_lazy = [img for img in images if (img.get("loading", "") or "") != "lazy"]
    results = [
        ok_check(
            "perf.img_dimensions",
            CATEGORY,
            "Images have dimensions",
            not no_dimensions,
            severity=MEDIUM,
            value="ok" if not no_dimensions else f"{len(no_dimensions)} without width/height",
            recommendation="Set width and height on images to prevent layout shift (CLS).",
            warn=True,
            url=page.url,
        ),
        ok_check(
            "perf.img_modern_format",
            CATEGORY,
            "Modern image formats",
            not legacy_format,
            severity=LOW,
            value="ok" if not legacy_format else f"{len(legacy_format)} legacy image(s)",
            recommendation="Serve WebP or AVIF instead of JPEG/PNG where possible.",
            warn=True,
            url=page.url,
        ),
        ok_check(
            "perf.img_lazy",
            CATEGORY,
            "Off-screen images lazy-loaded",
            len(not_lazy) <= 1,
            severity=LOW,
            value="ok" if len(not_lazy) <= 1 else f"{len(not_lazy)} eager image(s)",
            recommendation="Add loading=lazy to below-the-fold images.",
            warn=True,
            url=page.url,
        ),
    ]
    return results
