from __future__ import annotations

import hashlib
import json
from pathlib import Path

import pytest
import requests

import verify_production as production


class FakeResponse:
    def __init__(
        self,
        content: bytes,
        status_code: int = 200,
        headers: dict[str, str] | None = None,
    ) -> None:
        self.content = content
        self.status_code = status_code
        self.headers = headers or {}


class FakeSession:
    def __init__(self, responses: dict[str, FakeResponse]) -> None:
        self.responses = responses

    def __enter__(self) -> FakeSession:
        return self

    def __exit__(self, *_: object) -> None:
        return None

    def get(self, url: str, timeout: int) -> FakeResponse:
        assert timeout == 60
        return self.responses[url]


def _site(tmp_path: Path) -> tuple[Path, bytes, bytes]:
    site = tmp_path / "site"
    site.mkdir()
    content = b"production asset\n"
    (site / "index.html").write_bytes(content)
    headers_content = (
        b"/*\n  X-Test-Security: expected\n/assets/*\n  Cache-Control: test\n"
    )
    (site / "_headers").write_bytes(headers_content)
    manifest = {
        "canonical_url": "https://example.pages.dev/",
        "file_count": 1,
        "total_bytes": len(content),
        "files": [
            {
                "path": "index.html",
                "bytes": len(content),
                "sha256": hashlib.sha256(content).hexdigest(),
            }
        ],
        "deployment_config": [
            {
                "path": "_headers",
                "bytes": len(headers_content),
                "sha256": hashlib.sha256(headers_content).hexdigest(),
            }
        ],
    }
    manifest_bytes = (json.dumps(manifest, indent=2) + "\n").encode()
    (site / "report_manifest.json").write_bytes(manifest_bytes)
    return site, content, manifest_bytes


def test_fetch_retries_network_exception(monkeypatch: pytest.MonkeyPatch) -> None:
    class FlakySession:
        calls = 0

        def get(self, _: str, timeout: int) -> FakeResponse:
            assert timeout == 60
            self.calls += 1
            if self.calls == 1:
                raise requests.Timeout("transient")
            return FakeResponse(b"ok")

    monkeypatch.setattr(production.time, "sleep", lambda _: None)
    session = FlakySession()
    response = production.fetch(session, "https://example.test", attempts=2)  # type: ignore[arg-type]
    assert response.content == b"ok"
    assert session.calls == 2


def test_verify_checks_remote_manifest_and_all_assets(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    site, content, manifest_bytes = _site(tmp_path)
    base = "https://example.pages.dev/"
    responses = {
        base: FakeResponse(b"root", headers={"X-Test-Security": "expected"}),
        base + "report_manifest.json": FakeResponse(manifest_bytes),
        base + "index.html": FakeResponse(content),
    }
    monkeypatch.setattr(production.requests, "Session", lambda: FakeSession(responses))
    result = production.verify(site)
    assert result["remote_manifest_checked"] is True
    assert result["deployment_config_checked"] == 1
    assert result["security_headers_checked"] == 1
    assert result["checked_files"] == 1
    assert result["failures"] == []


def test_verify_rejects_remote_manifest_drift(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    site, content, _ = _site(tmp_path)
    base = "https://example.pages.dev/"
    responses = {
        base: FakeResponse(b"root", headers={"X-Test-Security": "expected"}),
        base + "report_manifest.json": FakeResponse(b"{}\n"),
        base + "index.html": FakeResponse(content),
    }
    monkeypatch.setattr(production.requests, "Session", lambda: FakeSession(responses))
    result = production.verify(site)
    failures = result["failures"]
    assert isinstance(failures, list)
    assert failures[0]["path"] == "report_manifest.json"


def test_verify_rejects_security_header_drift(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    site, content, manifest_bytes = _site(tmp_path)
    base = "https://example.pages.dev/"
    responses = {
        base: FakeResponse(b"root"),
        base + "report_manifest.json": FakeResponse(manifest_bytes),
        base + "index.html": FakeResponse(content),
    }
    monkeypatch.setattr(production.requests, "Session", lambda: FakeSession(responses))
    result = production.verify(site)
    failures = result["failures"]
    assert isinstance(failures, list)
    assert failures == [
        {
            "path": "/",
            "header": "X-Test-Security",
            "expected": "expected",
            "actual": None,
        }
    ]
