from __future__ import annotations

import hashlib
import io
from email.message import Message
from pathlib import Path
from typing import Self

import pytest

from osm_lead_source_service.errors import PbfDownloadRejected
from osm_lead_source_service.pbf import ApprovedTestPbfDownload
from osm_lead_source_service.pbf import download as download_module

_PAYLOAD = b"synthetic-pbf-response"
_SHA256 = hashlib.sha256(_PAYLOAD).hexdigest()
_URL = "http://127.0.0.1:8765/tiny.osm.pbf"


class _Response:
    def __init__(
        self,
        *,
        content_type: str = "application/octet-stream",
        content_encoding: str | None = None,
        content_length: int | None = len(_PAYLOAD),
        response_url: str = _URL,
        payload: bytes = _PAYLOAD,
    ) -> None:
        self.status = 200
        self._response_url = response_url
        self._body = io.BytesIO(payload)
        self.headers = Message()
        self.headers["Content-Type"] = content_type
        if content_encoding is not None:
            self.headers["Content-Encoding"] = content_encoding
        if content_length is not None:
            self.headers["Content-Length"] = str(content_length)

    def __enter__(self) -> Self:
        return self

    def __exit__(self, *args: object) -> None:
        del args

    def getcode(self) -> int:
        return self.status

    def geturl(self) -> str:
        return self._response_url

    def read(self, size: int = -1) -> bytes:
        return self._body.read(size)


class _Opener:
    def __init__(self, response: _Response) -> None:
        self._response = response

    def open(self, request: object, timeout: float) -> _Response:
        del request, timeout
        return self._response


def _settings() -> ApprovedTestPbfDownload:
    return ApprovedTestPbfDownload(
        region_id="fixture",
        url=_URL,
        expected_sha256=_SHA256,
        expected_size_bytes=len(_PAYLOAD),
    )


@pytest.mark.parametrize(
    ("response", "message"),
    [
        (_Response(content_type="text/plain"), "content type"),
        (_Response(content_encoding="gzip"), "content encoding"),
        (_Response(content_length=None), "Content-Length is required"),
        (
            _Response(response_url="http://127.0.0.1:8765/changed.osm.pbf"),
            "response URL changed",
        ),
    ],
)
def test_unapproved_http_response_metadata_is_rejected(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
    response: _Response,
    message: str,
) -> None:
    monkeypatch.setattr(download_module, "_test_opener", lambda: _Opener(response))

    with pytest.raises(PbfDownloadRejected, match=message):
        download_module.download_approved_test_pbf(_settings(), tmp_path)

    assert list(tmp_path.iterdir()) == []


def test_non_200_response_is_rejected(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    response = _Response()
    response.status = 206
    monkeypatch.setattr(download_module, "_test_opener", lambda: _Opener(response))

    with pytest.raises(PbfDownloadRejected, match="status is not 200"):
        download_module.download_approved_test_pbf(_settings(), tmp_path)


def test_invalid_content_length_is_rejected(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    response = _Response()
    response.headers.replace_header("Content-Length", "not-an-integer")
    monkeypatch.setattr(download_module, "_test_opener", lambda: _Opener(response))

    with pytest.raises(PbfDownloadRejected, match="Content-Length is invalid"):
        download_module.download_approved_test_pbf(_settings(), tmp_path)
