from __future__ import annotations

import hashlib
import socket
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any

import pytest

from osm_lead_source_service.errors import PbfDownloadRejected
from osm_lead_source_service.pbf import (
    ApprovedTestPbfDownload,
    ParsedExtractMetadata,
    download_approved_test_pbf,
)
from osm_lead_source_service.pbf.parser import OsmiumPbfParser

_SHA256 = "1f181fa63b838de88c5c7d8ecff2a014ca52a9349c6470fd2656ad3c9e0ddb8c"


class Sink:
    def __init__(self) -> None:
        self.count = 0
        self.finished = False
        self.aborted = False

    def accept(self, parsed_object: object) -> None:
        del parsed_object
        self.count += 1

    def finish(self, summary: object) -> None:
        del summary
        self.finished = True

    def abort(self) -> None:
        self.count = 0
        self.aborted = True


def approved(endpoint: Any, name: str = "tiny.osm.pbf") -> ApprovedTestPbfDownload:
    return ApprovedTestPbfDownload(
        region_id="fixture/test-region",
        url=endpoint.url(name),
        expected_sha256=_SHA256,
        expected_size_bytes=438,
    )


@pytest.mark.integration
@pytest.mark.pbf_network
def test_loopback_download_is_validated_cached_and_parsed(
    tmp_path: Path, pbf_test_endpoint: Any
) -> None:
    start = datetime(2026, 7, 11, 2, 0, tzinfo=timezone.utc)
    times = iter(
        (
            start,
            start + timedelta(seconds=1),
            start + timedelta(seconds=2),
            start + timedelta(seconds=3),
            start + timedelta(seconds=4),
        )
    )
    result = download_approved_test_pbf(
        approved(pbf_test_endpoint),
        tmp_path,
        clock=lambda: next(times),
    )
    assert result.cache_hit is False
    assert result.content_sha256 == _SHA256
    assert result.size_bytes == 438
    assert result.local_path.name == f"{_SHA256}.osm.pbf"
    assert result.etag == '"synthetic-pbf-v1"'
    assert result.last_modified == "Sat, 11 Jul 2026 00:00:00 GMT"

    sink = Sink()
    summary = OsmiumPbfParser(clock=lambda: next(times)).parse(result.as_verified_input(), sink)
    metadata = ParsedExtractMetadata.from_results(result, summary)
    assert sink.count == 4
    assert sink.finished is True
    assert sink.aborted is False
    assert metadata.counts.total == 4
    assert metadata.region_id == "fixture/test-region"

    cached = download_approved_test_pbf(
        approved(pbf_test_endpoint),
        tmp_path,
        clock=lambda: next(times),
    )
    assert cached.cache_hit is True
    assert cached.local_path == result.local_path


@pytest.mark.integration
@pytest.mark.pbf_network
def test_checksum_mismatch_removes_partial_file(tmp_path: Path, pbf_test_endpoint: Any) -> None:
    settings = ApprovedTestPbfDownload(
        region_id="fixture",
        url=pbf_test_endpoint.url(),
        expected_sha256="a" * 64,
        expected_size_bytes=438,
    )
    with pytest.raises(PbfDownloadRejected, match="checksum"):
        download_approved_test_pbf(settings, tmp_path)
    assert list(tmp_path.glob("*.part")) == []
    assert not (tmp_path / f"{'a' * 64}.osm.pbf").exists()


@pytest.mark.integration
@pytest.mark.pbf_network
def test_response_size_mismatch_is_rejected(tmp_path: Path, pbf_test_endpoint: Any) -> None:
    settings = ApprovedTestPbfDownload(
        region_id="fixture",
        url=pbf_test_endpoint.url(),
        expected_sha256=_SHA256,
        expected_size_bytes=437,
    )
    with pytest.raises(PbfDownloadRejected, match="response size"):
        download_approved_test_pbf(settings, tmp_path)


@pytest.mark.integration
@pytest.mark.pbf_network
def test_redirect_is_rejected(tmp_path: Path, pbf_test_endpoint: Any) -> None:
    with pytest.raises(PbfDownloadRejected, match="download failed"):
        download_approved_test_pbf(
            approved(pbf_test_endpoint, "redirect.osm.pbf"),
            tmp_path,
        )


@pytest.mark.integration
@pytest.mark.pbf_network
def test_corrupt_download_is_rejected_by_checksum(tmp_path: Path, pbf_test_endpoint: Any) -> None:
    corrupt = b"not-a-pbf"
    settings = ApprovedTestPbfDownload(
        region_id="fixture",
        url=pbf_test_endpoint.url("corrupt.osm.pbf"),
        expected_sha256=hashlib.sha256(b"different!").hexdigest(),
        expected_size_bytes=len(corrupt),
    )
    with pytest.raises(PbfDownloadRejected, match="checksum"):
        download_approved_test_pbf(settings, tmp_path)


@pytest.mark.integration
@pytest.mark.pbf_network
def test_pbf_network_marker_allows_only_fixture_endpoint(
    pbf_test_endpoint: Any,
) -> None:
    with pytest.raises(AssertionError, match="configured endpoint"):
        socket.create_connection(("127.0.0.1", pbf_test_endpoint.port + 1), timeout=0.01)
    with pytest.raises(AssertionError, match="configured endpoint"):
        socket.create_connection(("example.com", 80), timeout=0.01)
