from typing import Iterable, List

import requests

from ..localization.helpers import create_translated_finding
from ..model import Finding, ScanRequest


SECURITY_HEADERS = {
    "content-security-policy": ("high", "headers.missing.content-security-policy"),
    "strict-transport-security": ("high", "headers.missing.strict-transport-security"),
    "x-frame-options": ("medium", "headers.missing.x-frame-options"),
    "x-content-type-options": ("medium", "headers.missing.x-content-type-options"),
    "referrer-policy": ("low", "headers.missing.referrer-policy"),
    "permissions-policy": ("low", "headers.missing.permissions-policy"),
    "cross-origin-resource-policy": ("medium", "headers.missing.cross-origin-resource-policy"),
    "cross-origin-opener-policy": ("medium", "headers.missing.cross-origin-opener-policy"),
    "cross-origin-embedder-policy": ("low", "headers.missing.cross-origin-embedder-policy"),
}

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(
            create_translated_finding(
                check="headers",
                i18n_key="headers.https_request_failed",
                severity="high",
                i18n_params={
                    "url": url,
                    "error": str(exc),
                },
            )
        )
        return findings

    header_map = {k.lower(): v for k, v in response.headers.items()}
    for header, (severity, i18n_key) in SECURITY_HEADERS.items():
        if header not in header_map:
            findings.append(
                create_translated_finding(
                    check="headers",
                    i18n_key=i18n_key,
                    severity=severity,
                    i18n_params={
                        "header": header,
                        "header_upper": header.upper(),
                        "url": response.url,
                    },
                )
            )

    if response.url.startswith("http://"):
        findings.append(
            create_translated_finding(
                check="headers",
                i18n_key="headers.https_downgrade",
                severity="high",
                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(
                create_translated_finding(
                    check="headers",
                    i18n_key="headers.cookie_missing_secure",
                    severity="medium",
                    i18n_params={"cookie": cookie.name},
                )
            )
        if not cookie.has_nonstandard_attr("HttpOnly"):
            findings.append(
                create_translated_finding(
                    check="headers",
                    i18n_key="headers.cookie_missing_httponly",
                    severity="medium",
                    i18n_params={"cookie": cookie.name},
                )
            )
        same_site = cookie._rest.get("samesite") if hasattr(cookie, "_rest") else None
        if not same_site:
            findings.append(
                create_translated_finding(
                    check="headers",
                    i18n_key="headers.cookie_missing_samesite",
                    severity="low",
                    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(
                create_translated_finding(
                    check="headers",
                    i18n_key="headers.verbose_banner",
                    severity="low",
                    i18n_params={
                        "header": header.title(),
                        "value": value,
                    },
                    evidence=value
                )
            )
