"""Coverage for roboco.services.base — SingletonHolder pattern."""

from __future__ import annotations

import pytest
from roboco.services.base import (
    BaseService,
    ConflictError,
    NotFoundError,
    ServiceError,
    ServiceUnavailableError,
    SingletonHolder,
    SingletonService,
    UnauthorizedError,
    ValidationError,
)


class _DummyService:
    def __init__(self, value: str = "default") -> None:
        self.value = value


class _Holder(SingletonHolder[_DummyService]):
    def create_instance(self) -> _DummyService:
        return _DummyService("first")


def test_singleton_holder_get_creates_instance() -> None:
    holder = _Holder()
    assert holder.is_initialized is False
    inst = holder.get()
    assert inst.value == "first"
    assert holder.is_initialized is True


def test_singleton_holder_get_returns_same_instance() -> None:
    holder = _Holder()
    a = holder.get()
    b = holder.get()
    assert a is b


def test_singleton_holder_set_overrides_instance() -> None:
    holder = _Holder()
    custom = _DummyService("custom")
    holder.set(custom)
    assert holder.get() is custom


def test_singleton_holder_clear_resets_instance() -> None:
    holder = _Holder()
    holder.get()
    holder.clear()
    assert holder.is_initialized is False
    # Subsequent get() rebuilds.
    new = holder.get()
    assert new.value == "first"


def test_singleton_holder_create_instance_default_raises() -> None:
    """Base SingletonHolder must require subclass override."""
    holder: SingletonHolder[_DummyService] = SingletonHolder()
    with pytest.raises(NotImplementedError):
        holder.get()


# ---------------------------------------------------------------------------
# Error hierarchy — round-trip the constructors so the dataclass-style fields
# get exercised end-to-end.
# ---------------------------------------------------------------------------


def test_service_error_basic() -> None:
    err = ServiceError("oops", details={"k": "v"})
    assert err.message == "oops"
    assert err.details == {"k": "v"}


def test_not_found_error_with_id() -> None:
    err = NotFoundError("Task", resource_id="abc")
    assert "abc" in err.message
    assert err.resource_id == "abc"


def test_not_found_error_without_id() -> None:
    err = NotFoundError("Task")
    assert err.message == "Task not found"


def test_validation_error_with_field() -> None:
    err = ValidationError("bad", field="title")
    assert err.field == "title"


def test_conflict_error_records_resource() -> None:
    err = ConflictError("dup", resource_type="Project")
    assert err.resource_type == "Project"


def test_unauthorized_error_with_reason() -> None:
    err = UnauthorizedError("delete", reason="role")
    assert "delete" in err.message
    assert "role" in err.message


def test_service_unavailable_error_with_reason() -> None:
    err = ServiceUnavailableError("postgres", reason="down")
    assert "postgres" in err.message
    assert "down" in err.message


# ---------------------------------------------------------------------------
# BaseService + SingletonService instantiation — bind logger, etc.
# ---------------------------------------------------------------------------


def test_base_service_binds_session_and_logger() -> None:
    class _Svc(BaseService):
        service_name = "x"

    fake_session = object()
    svc = _Svc(fake_session)  # type: ignore[arg-type]
    assert svc.session is fake_session
    assert svc.log is not None


def test_singleton_service_binds_logger() -> None:
    class _Svc(SingletonService):
        service_name = "y"

    svc = _Svc()
    assert svc.log is not None
