from __future__ import annotations

import logging
import random
from typing import TYPE_CHECKING

from crystflux.v1.adapters.missing_logger_impl import (
    LoggingMissingLogger,
    RateLimitMissingLogger,
    SamplingMissingLogger,
    StdoutMissingLogger,
)
from crystflux.v1.boundary.event_schema import MissingEvent
from crystflux.v1.core.void_reason import VoidReason

if TYPE_CHECKING:
    from pytest import CaptureFixture, LogCaptureFixture


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

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


def _event() -> MissingEvent:
    return MissingEvent(
        reason=VoidReason.MISSING_KEY,
        key="k",
        target_repr="{}",
        target_type="dict",
        source_location="test.py:1",
    )

def _event_index() -> MissingEvent:
    return MissingEvent(
        reason=VoidReason.MISSING_INDEX,
        index=1,
        target_repr="[]",
        target_type="list",
        source_location="test.py:1",
    )


def _event_no_detail() -> MissingEvent:
    return MissingEvent(
        reason=VoidReason.MISSING_KEY,
        key=None,
        index=None,
        target_repr="{}",
        target_type="dict",
        source_location="test.py:1",
    )


def test_sampling_missing_logger_deterministic_rng() -> None:
    sink = ListMissingLogger()
    sampler = SamplingMissingLogger(inner=sink, p=0.5, rng=random.Random(0))
    for _ in range(10):
        sampler.log(_event())
    assert 0 < len(sink.events) < 10


def test_rate_limit_missing_logger_allows_first_event() -> None:
    sink = ListMissingLogger()
    limiter = RateLimitMissingLogger(sink, max_per_sec=0.1, burst=1)
    for _ in range(10):
        limiter.log(_event())
    assert len(sink.events) == 1


def test_logging_missing_logger_verbose(caplog: "LogCaptureFixture") -> None:
    caplog.set_level(logging.INFO)
    logger = LoggingMissingLogger(verbose=True)
    logger.log(_event())
    assert caplog.records


def test_stdout_missing_logger_non_verbose_key_detail(capsys: "CaptureFixture[str]") -> None:
    logger = StdoutMissingLogger(verbose=False)
    logger.log(_event())
    out: str = capsys.readouterr().out
    assert "[Missing] reason=" in out
    assert " key='k'" in out
    assert "target=dict" in out
    assert "repr={}" in out


def test_stdout_missing_logger_non_verbose_index_detail(capsys: "CaptureFixture[str]") -> None:
    logger = StdoutMissingLogger(verbose=False)
    logger.log(_event_index())
    out: str = capsys.readouterr().out
    assert " index=1" in out


def test_stdout_missing_logger_non_verbose_no_detail(capsys: "CaptureFixture[str]") -> None:
    logger = StdoutMissingLogger(verbose=False)
    logger.log(_event_no_detail())
    out: str = capsys.readouterr().out
    assert " key=" not in out
    assert " index=" not in out


def test_stdout_missing_logger_verbose_multiline(capsys: "CaptureFixture[str]") -> None:
    logger = StdoutMissingLogger(verbose=True)
    event = MissingEvent(
        reason=VoidReason.MISSING_KEY,
        key="k",
        target_repr="{}",
        target_type="dict",
        source_location="test_file.py:10",
        trace_frames=("frame1:1",),
        timestamp=None,
    )
    logger.log(event)
    out: str = capsys.readouterr().out
    assert out.splitlines()[0].startswith("[Missing verbose] reason=")
    assert "  source=test_file.py:10" in out


def test_logging_missing_logger_non_verbose(caplog: "LogCaptureFixture") -> None:
    caplog.set_level(logging.INFO)
    logger = LoggingMissingLogger(verbose=False)
    logger.log(_event())
    assert any("missing_event reason=" in record.message for record in caplog.records)


def test_sampling_missing_logger_p0_drops_all() -> None:
    sink = ListMissingLogger()
    sampler = SamplingMissingLogger(inner=sink, p=0.0)
    for _ in range(10):
        sampler.log(_event())
    assert not sink.events


def test_sampling_missing_logger_p1_passes_all() -> None:
    sink = ListMissingLogger()
    sampler = SamplingMissingLogger(inner=sink, p=1.0)
    for _ in range(10):
        sampler.log(_event())
    assert len(sink.events) == 10


def test_rate_limit_missing_logger_rejects_non_positive_rate() -> None:
    sink = ListMissingLogger()
    try:
        RateLimitMissingLogger(sink, max_per_sec=0.0, burst=1)
    except ValueError:
        return
    raise AssertionError("Expected ValueError")


def test_rate_limit_missing_logger_rejects_non_positive_burst() -> None:
    sink = ListMissingLogger()
    try:
        RateLimitMissingLogger(sink, max_per_sec=1.0, burst=0)
    except ValueError:
        return
    raise AssertionError("Expected ValueError")
