# retoor <retoor@molodetz.nl>

from __future__ import annotations

from .base import (
    PAGE_CHECKS,
    SITE_CHECKS,
    SEVERITY_WEIGHT,
    STATUS_CREDIT,
    INFO,
    SKIP,
    Check,
    PageContext,
    SiteContext,
)
from . import crawl  # noqa: F401
from . import meta  # noqa: F401
from . import headings  # noqa: F401
from . import links  # noqa: F401
from . import structured_data  # noqa: F401
from . import social  # noqa: F401
from . import performance  # noqa: F401
from . import mobile_a11y  # noqa: F401
from . import security  # noqa: F401
from . import ai_readiness  # noqa: F401
from . import crosspage  # noqa: F401


def _collect(result: object) -> list[Check]:
    if result is None:
        return []
    if isinstance(result, Check):
        return [result]
    return [c for c in result if isinstance(c, Check)]


def run_page_checks(page: PageContext, site: SiteContext) -> list[Check]:
    checks: list[Check] = []
    for func in PAGE_CHECKS:
        try:
            checks.extend(_collect(func(page, site)))
        except Exception as exc:  # noqa: BLE001 - one bad check never aborts the page
            checks.append(
                Check(
                    id=f"{func.__name__}.error",
                    category="internal",
                    title=f"Check {func.__name__} failed",
                    status=INFO,
                    severity="info",
                    value=str(exc)[:200],
                    url=page.url,
                )
            )
    return checks


def run_site_checks(site: SiteContext) -> list[Check]:
    checks: list[Check] = []
    for func in SITE_CHECKS:
        try:
            checks.extend(_collect(func(site)))
        except Exception as exc:  # noqa: BLE001
            checks.append(
                Check(
                    id=f"{func.__name__}.error",
                    category="internal",
                    title=f"Site check {func.__name__} failed",
                    status=INFO,
                    severity="info",
                    value=str(exc)[:200],
                )
            )
    return checks


def compute_score(checks: list[Check]) -> dict:
    by_category: dict[str, dict] = {}
    earned = 0.0
    weight = 0.0
    counts = {"pass": 0, "warn": 0, "fail": 0, "info": 0, "skip": 0}
    for check in checks:
        counts[check.status] = counts.get(check.status, 0) + 1
        bucket = by_category.setdefault(
            check.category,
            {"earned": 0.0, "weight": 0.0, "pass": 0, "warn": 0, "fail": 0, "info": 0, "skip": 0},
        )
        bucket[check.status] = bucket.get(check.status, 0) + 1
        if check.status in (INFO, SKIP):
            continue
        w = SEVERITY_WEIGHT.get(check.severity, 1.0)
        if w <= 0:
            continue
        credit = STATUS_CREDIT.get(check.status, 0.0)
        earned += w * credit
        weight += w
        bucket["earned"] += w * credit
        bucket["weight"] += w
    categories = {}
    for name, bucket in by_category.items():
        cat_score = (
            round(100 * bucket["earned"] / bucket["weight"])
            if bucket["weight"] > 0
            else None
        )
        categories[name] = {
            "score": cat_score,
            "pass": bucket["pass"],
            "warn": bucket["warn"],
            "fail": bucket["fail"],
            "info": bucket["info"],
            "skip": bucket["skip"],
        }
    score = round(100 * earned / weight) if weight > 0 else 0
    return {
        "score": score,
        "grade": _grade(score),
        "counts": counts,
        "categories": categories,
    }


def _grade(score: int) -> str:
    if score >= 90:
        return "A"
    if score >= 80:
        return "B"
    if score >= 70:
        return "C"
    if score >= 55:
        return "D"
    return "F"
