from __future__ import annotations

import base64
import hashlib
from pathlib import Path

import pytest

from osm_lead_source_service.errors import PbfTransactionError
from osm_lead_source_service.pbf import (
    OsmiumPbfParser,
    ParsedOsmObject,
    PbfParseSummary,
    VerifiedPbfInput,
)

_FIXTURE = Path(__file__).parents[2] / "fixtures" / "pbf" / "tiny.osm.pbf.b64"


def _materialize_fixture(tmp_path: Path) -> Path:
    path = tmp_path / "tiny.osm.pbf"
    path.write_bytes(base64.b64decode(_FIXTURE.read_text(encoding="ascii").strip()))
    return path


def _verified_input(path: Path) -> VerifiedPbfInput:
    data = path.read_bytes()
    return VerifiedPbfInput(
        local_path=path,
        content_sha256=hashlib.sha256(data).hexdigest(),
        size_bytes=len(data),
        provenance="synthetic rollback test fixture",
    )


def _assert_combined_failures(error: PbfTransactionError, *messages: str) -> None:
    cause = error.__cause__
    assert isinstance(cause, ExceptionGroup)
    rendered = "\n".join(str(item) for item in cause.exceptions)
    for message in messages:
        assert message in rendered


def test_accept_failure_and_abort_failure_are_both_surfaced(tmp_path: Path) -> None:
    class Sink:
        def accept(self, parsed_object: ParsedOsmObject) -> None:
            del parsed_object
            raise RuntimeError("accept failed")

        def finish(self, summary: PbfParseSummary) -> None:
            del summary

        def abort(self) -> None:
            raise RuntimeError("abort failed")

    with pytest.raises(PbfTransactionError) as caught:
        OsmiumPbfParser().parse(_verified_input(_materialize_fixture(tmp_path)), Sink())

    _assert_combined_failures(caught.value, "accept failed", "abort failed")


def test_finish_failure_and_abort_failure_are_both_surfaced(tmp_path: Path) -> None:
    class Sink:
        def accept(self, parsed_object: ParsedOsmObject) -> None:
            del parsed_object

        def finish(self, summary: PbfParseSummary) -> None:
            del summary
            raise RuntimeError("finish failed")

        def abort(self) -> None:
            raise RuntimeError("abort failed")

    with pytest.raises(PbfTransactionError) as caught:
        OsmiumPbfParser().parse(_verified_input(_materialize_fixture(tmp_path)), Sink())

    _assert_combined_failures(caught.value, "finish failed", "abort failed")
