import json
import re
from typing import Any

import pytest

from crystflux.v1 import Crystallizer, DreamValue, LatentValue, StrictValue
from crystflux.v1.adapters import enable_dream_mode
from crystflux.v1.boundary import DesireEvent, InvalidJsonPayloadError, ModeNotSupportedError


def test_crystallizer_mode_selection(tao_data: dict[str, Any]) -> None:
    latent = Crystallizer.from_dict(tao_data["laozi"], mode="latent")
    strict = Crystallizer.from_dict(tao_data["laozi"], mode="strict")
    with enable_dream_mode():
        dream = Crystallizer.from_dict(tao_data["laozi"], mode="dream")

    assert isinstance(latent, LatentValue)
    assert isinstance(strict, StrictValue)
    assert isinstance(dream, DreamValue)

    loaded = Crystallizer.from_json(json.dumps(tao_data["zhuangzi"]), mode="latent")
    assert isinstance(loaded, LatentValue)
    assert loaded.get("author").as_str() == "荘子"


def test_crystallizer_first_rest_rest_last_smoke(tao_data: dict[str, Any]) -> None:
    v = Crystallizer.latent(tao_data["anthology"]["fragments"]).to_array()
    first, _rest = v.first_rest()
    assert first.get("source").as_str() == "道徳経"
    rest_vals, last = v.rest_last()
    assert last.get("source").as_str() == "荘子"
    assert rest_vals.first_rest()[0].get("source").as_str() == "道徳経"


def test_crystallizer_invalid_mode_and_payload() -> None:
    with pytest.raises(ModeNotSupportedError):
        Crystallizer.from_dict({}, mode="unknown")  # type:ignore

    with pytest.raises(InvalidJsonPayloadError):
        Crystallizer.from_json("not valid json")


def test_crystallizer_convenience_methods_return_values() -> None:
    """Test convenience methods return correct types to cover missing lines."""
    data = {"key": "value"}

    # Test strict() return value (line 66)
    strict_result = Crystallizer.strict(data)
    assert isinstance(strict_result, StrictValue)

    # Test dream() return value (line 70)
    with enable_dream_mode():
        dream_result = Crystallizer.dream(data)
    assert isinstance(dream_result, DreamValue)
    # Test safe alias (line 83)
    safe_result = Crystallizer.safe(data)  # type: ignore[attr-defined]
    assert isinstance(safe_result, LatentValue)


def test_crystallizer_unmapped_resolved_mode(monkeypatch: pytest.MonkeyPatch) -> None:
    """Ensure unmapped resolved mode triggers ModeNotSupportedError with supported list."""
    from crystflux.v1.adapters import crystallizer_impl

    monkeypatch.setattr(crystallizer_impl, "resolve_mode", lambda mode: "phantom")  # type:ignore

    with pytest.raises(
        ModeNotSupportedError, match="Unsupported mode: phantom.*strict, latent, dream"
    ):
        Crystallizer.from_dict({}, mode="phantom")  # type:ignore


def test_internal_crystallizer_helpers() -> None:
    """Test internal helper functions directly to improve coverage."""
    from crystflux.v1.adapters.crystallizer_impl import (
        _trim_repr,  # type: ignore[reportPrivateUsage]
        _build_desire_event,  # type: ignore[reportPrivateUsage]
    )

    # Test _trim_repr (lines 110-113)
    short_string = "short"
    long_string = "x" * 100
    short_result = _trim_repr(short_string, 80)
    long_result = _trim_repr(long_string, 80)
    exact_result = _trim_repr("exact", 5)

    assert short_result == "'short'"
    assert len(long_result) == 80
    assert long_result.startswith("'xxxxxxxx")
    assert long_result.endswith("...")
    assert exact_result[0] == "'" and exact_result[1] == "e" and exact_result[2:] == "..."

    # Test _build_desire_event (lines 95-98)
    event = _build_desire_event("test_method", {"data": 123}, ("step1",), None)
    assert event.method_name == "test_method"
    assert event.target_type == "dict"
    assert event.target_repr == "{'data': 123}"
    assert event.chain_steps == ("step1",)
    assert event.trace_frames is None
    assert event.source_location is not None
    assert event.timestamp is not None

    # Test _build_desire_event with None value
    event_none = _build_desire_event("test_method", None, (), None)
    assert event_none.target_type == "void"
    assert event_none.target_repr == "None"


def test_get_caller_location_with_context_shape() -> None:
    """_get_caller_location_with_context returns empty or file:line."""
    from crystflux.v1.adapters.crystallizer_impl import _get_caller_location_with_context  # type: ignore[reportPrivateUsage]

    location = _get_caller_location_with_context()
    if location:
        assert re.match(r".+:\d+$", location)


def test_collect_trace_frames_with_context_shape_and_limit() -> None:
    """_collect_trace_frames_with_context respects limit and formatting."""
    from crystflux.v1.adapters.crystallizer_impl import _collect_trace_frames_with_context  # type: ignore[reportPrivateUsage]

    frames = _collect_trace_frames_with_context(max_frames=3)
    assert isinstance(frames, tuple)
    assert len(frames) <= 3
    for frame in frames:
        assert re.match(r".+:\d+$", frame)
        assert "crystflux/v1" not in frame and "crystflux\\v1" not in frame

    assert _collect_trace_frames_with_context(max_frames=0) == ()


def test_make_desire_hook_verbose_adds_trace_frames() -> None:
    """Verbose desire hook should attach trace frames."""

    class VerboseLogger:
        verbose = True
        logged_events: list[DesireEvent] = []

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

    logger = VerboseLogger()
    hook = Crystallizer._make_desire_hook(logger)  # type: ignore[reportPrivateUsage]
    hook("test_method", {"data": "test"}, ("step1",))

    assert logger.logged_events
    event = logger.logged_events[0]
    assert event.trace_frames is not None
    assert isinstance(event.trace_frames, tuple)
    assert all(re.match(r".+:\d+$", frame) for frame in event.trace_frames)


def test_make_desire_hook_non_verbose_omits_trace_frames() -> None:
    """Non-verbose desire hook should not attach trace frames."""

    class QuietLogger:
        verbose = False
        logged_events: list[DesireEvent] = []

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

    logger = QuietLogger()
    hook = Crystallizer._make_desire_hook(logger)  # type: ignore[reportPrivateUsage]
    hook("test_method", {"data": "test"}, ("step1",))

    assert logger.logged_events
    event = logger.logged_events[0]
    assert event.trace_frames is None


def test_make_desire_hook_exception_handling() -> None:
    """Hook should swallow logger exceptions."""

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

    hook = Crystallizer._make_desire_hook(FailingLogger())  # type: ignore[reportPrivateUsage]

    try:
        hook("test_method", {"data": "test"}, ("step1",))
    except RuntimeError:
        pytest.fail("Hook should swallow logger exceptions")
