import logging

import pytest

from crystflux.v1.adapters.desire_logger_impl import LoggingDesireLogger, StdoutDesireLogger
from crystflux.v1.boundary import DesireEvent


def _make_event(
    *,
    method_name: str = "m",
    target_repr: str = "{}",
    target_type: str = "dict",
    source_location: str | None = None,
    context_snippet: str | None = None,
    timestamp: object | None = None,
    chain_steps: tuple[str, ...] | None = None,
    trace_frames: tuple[str, ...] | None = None,
) -> DesireEvent:
    return DesireEvent(
        method_name=method_name,
        target_repr=target_repr,
        target_type=target_type,
        source_location=source_location,
        context_snippet=context_snippet,
        timestamp=timestamp,  # type: ignore[arg-type]
        chain_steps=chain_steps,
        trace_frames=trace_frames,
    )


def test_stdout_desire_logger_non_verbose(capsys: pytest.CaptureFixture[str]) -> None:
    logger = StdoutDesireLogger(verbose=False)
    logger.log(_make_event())

    out = capsys.readouterr().out.strip()
    assert "[Desire]" in out
    assert "method=m" in out
    assert "target=dict" in out
    assert "repr={}" in out


def test_stdout_desire_logger_verbose(capsys: pytest.CaptureFixture[str]) -> None:
    logger = StdoutDesireLogger(verbose=True)
    logger.log(_make_event(chain_steps=("a",), trace_frames=("x:1",)))

    output = "\n".join(capsys.readouterr().out.strip().splitlines())
    required_keys = [
        "method=m",
        "target=dict",
        "repr={}",
        "chain_steps=('a',)",
        "trace_frames=('x:1',)",
        "timestamp=",
    ]
    assert all(key in output for key in required_keys)


def test_logging_desire_logger_non_verbose(monkeypatch: pytest.MonkeyPatch) -> None:
    calls: list[tuple[tuple[object, ...], dict[str, object]]] = []
    mock_logger = logging.getLogger("desire_non_verbose")
    def _log(*args: object, **kwargs: object) -> None:
        calls.append((args, kwargs))

    monkeypatch.setattr(mock_logger, "info", _log)

    logger = LoggingDesireLogger(logger=mock_logger, verbose=False)
    logger.log(_make_event(target_repr="x"))

    assert len(calls) == 1
    args, _kwargs = calls[0]
    fmt, method, target_type, target_repr = args
    assert "desire_event method=%s target_type=%s target_repr=%s" in fmt  # type: ignore[operator]
    assert method == "m"
    assert target_type == "dict"
    assert target_repr == "x"


def test_logging_desire_logger_verbose(monkeypatch: pytest.MonkeyPatch) -> None:
    calls: list[tuple[tuple[object, ...], dict[str, object]]] = []
    mock_logger = logging.getLogger("desire_verbose")
    def _log(*args: object, **kwargs: object) -> None:
        calls.append((args, kwargs))

    monkeypatch.setattr(mock_logger, "info", _log)

    logger = LoggingDesireLogger(logger=mock_logger, verbose=True)
    logger.log(_make_event(chain_steps=("s",), trace_frames=("x:1",)))

    assert len(calls) == 1
    args, _kwargs = calls[0]
    fmt = args[0]
    assert (
        "desire_event method=%s target_type=%s target_repr=%s source=%s chain=%s trace=%s ts=%s"
        in fmt  # type: ignore[operator]
    )
    # Remaining arguments presence/shape
    assert len(args) == 8
    assert args[1] == "m"
    assert args[2] == "dict"
    assert args[3] == "{}"
    assert args[4] is None  # source_location from desire hook path is None
    assert args[5] in (None, ("s",))
    assert args[6] in (None, ("x:1",))
    assert args[7] is None or not isinstance(args[7], Exception)
