import json
from pathlib import Path

from crystflux.v1 import Crystallizer
from crystflux.v1.adapters import enable_dream_mode
from crystflux.v1.boundary.event_schema import DesireEvent
from crystflux.v1.adapters.desire_logger_impl import StdoutDesireLogger, LoggingDesireLogger
import logging
from datetime import datetime
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from pytest import CaptureFixture, LogCaptureFixture


class ListLogger:
    def __init__(self) -> None:
        self.events: list[DesireEvent] = []

    def log(self, event: DesireEvent) -> None:
        self.events.append(event)


def test_dream_logs_desire_event() -> None:
    data = json.loads((Path(__file__).parent.parent / "data" / "taoist.json").read_text())
    logger = ListLogger()

    with enable_dream_mode():
        dream = Crystallizer.dream(data["laozi"], desire_logger=logger)
        result = dream.missing_method().some_other_call()  # type: ignore[attr-defined]

    assert result.is_void()  # type: ignore[attr-defined]
    assert len(logger.events) == 2
    first_event, second_event = logger.events
    assert first_event.method_name == "missing_method"
    assert first_event.chain_steps == ("missing_method",)
    assert second_event.method_name == "some_other_call"
    assert second_event.chain_steps == ("missing_method", "some_other_call")
    for event in logger.events:
        assert event.target_type
        assert event.target_repr
        assert event.timestamp is not None
        assert event.source_location is not None


def test_desire_logger_error_does_not_break_chain() -> None:
    data = json.loads((Path(__file__).parent.parent / "data" / "taoist.json").read_text())

    class FailingLogger:
        def log(self, event: DesireEvent) -> None:
            raise RuntimeError("logger failure")

    with enable_dream_mode():
        dream = Crystallizer.dream(data["laozi"], desire_logger=FailingLogger())
        result = dream.nonexistent()
    # Even though logger fails, result should still be VOID and no exception should propagate
    assert result.is_void()


def test_stdout_desire_logger_verbose(capsys: "CaptureFixture[str]") -> None:
    logger = StdoutDesireLogger(verbose=True)
    event = DesireEvent(
        method_name="test_method",
        target_type="TestType",
        target_repr="<TestObject>",
        source_location="test_file.py:10",
        chain_steps=("test_chain", "step"),
        trace_frames=("frame1", "frame2"),
        timestamp=datetime.now(),
    )
    logger.log(event)
    captured = capsys.readouterr()
    assert "[Desire verbose] method=test_method" in captured.out
    assert "  target=TestType repr=<TestObject>" in captured.out
    assert "  source=test_file.py:10" in captured.out
    assert "  chain_steps=('test_chain', 'step')" in captured.out
    assert "  trace_frames=('frame1', 'frame2')" in captured.out
    assert "  timestamp=" in captured.out  # Timestamp varies, just check for its presence


def test_logging_desire_logger_verbose(caplog: "LogCaptureFixture") -> None:
    # Set logging level to INFO to ensure our messages are captured
    caplog.set_level(logging.INFO)
    logger = LoggingDesireLogger(verbose=True)
    event = DesireEvent(
        method_name="test_method_log",
        target_type="TestTypeLog",
        target_repr="<TestObjectLog>",
        source_location="test_log_file.py:20",
        chain_steps=("test_log_chain", "log_step"),
        trace_frames=("log_frame1", "log_frame2"),
        timestamp=datetime.now(),
    )
    logger.log(event)
    assert len(caplog.records) == 1
    record = caplog.records[0]
    assert "desire_event method=test_method_log" in record.message
    assert "target_type=TestTypeLog" in record.message
    assert "target_repr=<TestObjectLog>" in record.message
    assert "source=test_log_file.py:20" in record.message
    assert "chain=('test_log_chain', 'log_step')" in record.message
    assert "trace=('log_frame1', 'log_frame2')" in record.message
    assert "ts=" in record.message  # Timestamp varies, just check for its presence
