from typing import Iterable, List

import requests

from ..model import Finding, ScanRequest


SECURITY_HEADERS = {
    "content-security-policy": (
        "high",
        "CSP header missing",
        "Define a Content-Security-Policy to limit script and resource origins.",
    ),
    "strict-transport-security": (
        "high",
        "HSTS header missing",
        "Set Strict-Transport-Security to enforce HTTPS and preload eligibility.",
    ),
    "x-frame-options": (
        "medium",
        "X-Frame-Options header missing",
        "Set X-Frame-Options to DENY or SAMEORIGIN to mitigate clickjacking.",
    ),
    "x-content-type-options": (
        "medium",
        "X-Content-Type-Options header missing",
        "Configure X-Content-Type-Options: nosniff to prevent MIME sniffing.",
    ),
    "referrer-policy": (
        "low",
        "Referrer-Policy header missing",
        "Add Referrer-Policy to limit sensitive referrer leakage.",
    ),
    "permissions-policy": (
        "low",
        "Permissions-Policy header missing",
        "Adopt a Permissions-Policy to control powerful browser features.",
    ),
    "cross-origin-resource-policy": (
        "medium",
        "Cross-Origin-Resource-Policy header missing",
        "Set Cross-Origin-Resource-Policy to control how other origins can load resources.",
    ),
    "cross-origin-opener-policy": (
        "medium",
        "Cross-Origin-Opener-Policy header missing",
        "Set Cross-Origin-Opener-Policy to isolate browsing contexts and mitigate XS-Leaks.",
    ),
    "cross-origin-embedder-policy": (
        "low",
        "Cross-Origin-Embedder-Policy header missing",
        "Adopt COEP to require CORS or CORP on embedded resources.",
    ),
}

BANNER_HEADERS = ("server", "x-powered-by")


def evaluate_security_headers(request: ScanRequest) -> Iterable[Finding]:
    findings: List[Finding] = []
    url = f"https://{request.domain}"
    try:
        response = requests.get(
            url,
            headers={"User-Agent": request.user_agent},
            timeout=request.timeout,
            allow_redirects=True,
        )
    except requests.RequestException as exc:
        findings.append(
            Finding(
                check="headers",
                title="HTTPS request failed",
                severity="high",
                description=f"Unable to fetch {url}: {exc}",
                remediation="Verify DNS resolution and that the site responds over HTTPS.",
                i18n_key="headers.https_request_failed",
                i18n_params={
                    "url": url,
                    "error": str(exc),
                },
            )
        )
        return findings

    header_map = {k.lower(): v for k, v in response.headers.items()}
    for header, (severity, title, remediation) in SECURITY_HEADERS.items():
        if header not in header_map:
            findings.append(
                Finding(
                    check="headers",
                    title=title,
                    severity=severity,
                    description=f"{header} not present on {response.url}",
                    remediation=remediation,
                    i18n_key=f"headers.missing.{header}",
                    i18n_params={
                        "header": header,
                        "header_upper": header.upper(),
                        "url": response.url,
                    },
                )
            )

    if response.url.startswith("http://"):
        findings.append(
            Finding(
                check="headers",
                title="HTTPS downgrade detected",
                severity="high",
                description=f"Request followed redirect chain to HTTP endpoint: {response.url}",
                remediation="Ensure all redirects enforce HTTPS.",
                i18n_key="headers.https_downgrade",
                i18n_params={"url": response.url},
            )
        )

    _check_cookie_flags(response, findings)
    _check_server_fingerprint(header_map, findings)
    return findings


def _check_cookie_flags(response: requests.Response, findings: List[Finding]) -> None:
    for cookie in response.cookies:
        if not cookie.secure:
            findings.append(
                Finding(
                    check="headers",
                    title="Cookie missing Secure flag",
                    severity="medium",
                    description=f"Cookie '{cookie.name}' lacks the Secure flag.",
                    remediation="Mark session cookies as Secure to enforce HTTPS transport.",
                    i18n_key="headers.cookie_missing_secure",
                    i18n_params={"cookie": cookie.name},
                )
            )
        if not cookie.has_nonstandard_attr("HttpOnly"):
            findings.append(
                Finding(
                    check="headers",
                    title="Cookie missing HttpOnly flag",
                    severity="medium",
                    description=f"Cookie '{cookie.name}' lacks the HttpOnly flag.",
                    remediation="Mark authentication cookies as HttpOnly to block script access.",
                    i18n_key="headers.cookie_missing_httponly",
                    i18n_params={"cookie": cookie.name},
                )
            )
        same_site = cookie._rest.get("samesite") if hasattr(cookie, "_rest") else None
        if not same_site:
            findings.append(
                Finding(
                    check="headers",
                    title="Cookie missing SameSite flag",
                    severity="low",
                    description=f"Cookie '{cookie.name}' lacks the SameSite attribute.",
                    remediation="Set SameSite=Lax or Strict to reduce CSRF exposure.",
                    impact="Cookies without SameSite are automatically sent on cross-site navigations.",
                    i18n_key="headers.cookie_missing_samesite",
                    i18n_params={"cookie": cookie.name},
                )
            )


def _check_server_fingerprint(headers: dict, findings: List[Finding]) -> None:
    for header in BANNER_HEADERS:
        value = headers.get(header)
        if not value:
            continue

        if any(char.isdigit() for char in value) or "/" in value:
            findings.append(
                Finding(
                    check="headers",
                    title=f"Verbose {header.title()} header",
                    severity="low",
                    description=f"{header} discloses software details: {value}",
                    remediation=f"Strip version identifiers from the {header} header or disable it.",
                    impact="Server banners help attackers tailor exploits to known versions.",
                    evidence=value,
                    i18n_key="headers.verbose_banner",
                    i18n_params={
                        "header": header.title(),
                        "value": value,
                    },
                )
            )
