"""Tests for policy events module."""

from __future__ import annotations

import json
import tempfile
from datetime import datetime
from pathlib import Path
from typing import List

import pytest

from hieromem_core.policy.events import (
    PolicyEvent,
    PolicyEventType,
    PolicyEventEmitter,
    LoggingEventHandler,
    FileEventHandler,
    CallbackEventHandler,
    FilteredEventHandler,
    get_default_emitter,
    setup_default_logging,
)
from hieromem_core.policy.evaluator import (
    PolicyAction,
    PolicyDecision,
    PolicyResult,
)


class TestPolicyEvent:
    """Tests for PolicyEvent dataclass."""

    def test_create_basic_event(self) -> None:
        event = PolicyEvent(event_type=PolicyEventType.DECISION)
        assert event.event_type == PolicyEventType.DECISION
        assert isinstance(event.timestamp, datetime)

    def test_create_event_with_all_fields(self) -> None:
        event = PolicyEvent(
            event_type=PolicyEventType.VIOLATION,
            policy_name="test-policy",
            namespace="default",
            episode_id="ep-123",
            action=PolicyAction.INSERT,
            result=PolicyResult.DENY,
            reason="Forbidden relation",
            details={"relation": "secret"},
            trace_id="trace-456",
        )
        assert event.policy_name == "test-policy"
        assert event.namespace == "default"
        assert event.episode_id == "ep-123"
        assert event.action == PolicyAction.INSERT
        assert event.result == PolicyResult.DENY
        assert event.reason == "Forbidden relation"
        assert event.trace_id == "trace-456"

    def test_from_decision_deny(self) -> None:
        decision = PolicyDecision(
            result=PolicyResult.DENY,
            policy_name="test-policy",
            action=PolicyAction.INSERT,
            reason="Denied",
            namespace="default",
        )
        event = PolicyEvent.from_decision(decision, trace_id="t1")
        assert event.event_type == PolicyEventType.VIOLATION
        assert event.trace_id == "t1"
        assert event.policy_name == "test-policy"

    def test_from_decision_warn(self) -> None:
        decision = PolicyDecision(
            result=PolicyResult.WARN,
            policy_name="test-policy",
            action=PolicyAction.INSERT,
            reason="Warning",
        )
        event = PolicyEvent.from_decision(decision)
        assert event.event_type == PolicyEventType.WARNING

    def test_from_decision_allow(self) -> None:
        decision = PolicyDecision(
            result=PolicyResult.ALLOW,
            policy_name="test-policy",
            action=PolicyAction.INSERT,
            reason="Allowed",
        )
        event = PolicyEvent.from_decision(decision)
        assert event.event_type == PolicyEventType.DECISION

    def test_to_dict(self) -> None:
        event = PolicyEvent(
            event_type=PolicyEventType.DECISION,
            policy_name="test",
            action=PolicyAction.INSERT,
            result=PolicyResult.ALLOW,
        )
        d = event.to_dict()
        assert d["event_type"] == "decision"
        assert d["policy_name"] == "test"
        assert d["action"] == "insert"
        assert d["result"] == "allow"
        assert "timestamp" in d


class TestPolicyEventEmitter:
    """Tests for PolicyEventEmitter."""

    def test_emit_to_handlers(self) -> None:
        emitter = PolicyEventEmitter()  # type: ignore[no-untyped-call]
        received: List[PolicyEvent] = []

        def handler(event: PolicyEvent) -> None:
            received.append(event)

        emitter.add_handler(CallbackEventHandler(handler))

        event = PolicyEvent(event_type=PolicyEventType.DECISION)
        emitter.emit(event)

        assert len(received) == 1
        assert received[0] == event

    def test_emit_to_multiple_handlers(self) -> None:
        emitter = PolicyEventEmitter()  # type: ignore[no-untyped-call]
        received1: List[PolicyEvent] = []
        received2: List[PolicyEvent] = []

        emitter.add_handler(CallbackEventHandler(lambda e: received1.append(e)))
        emitter.add_handler(CallbackEventHandler(lambda e: received2.append(e)))

        event = PolicyEvent(event_type=PolicyEventType.VIOLATION)
        emitter.emit(event)

        assert len(received1) == 1
        assert len(received2) == 1

    def test_remove_handler(self) -> None:
        emitter = PolicyEventEmitter()  # type: ignore[no-untyped-call]
        received: List[PolicyEvent] = []
        handler = CallbackEventHandler(lambda e: received.append(e))

        emitter.add_handler(handler)
        emitter.remove_handler(handler)

        emitter.emit(PolicyEvent(event_type=PolicyEventType.DECISION))
        assert len(received) == 0


class TestCallbackEventHandler:
    """Tests for CallbackEventHandler."""

    def test_handle_calls_callback(self) -> None:
        received: List[PolicyEvent] = []
        handler = CallbackEventHandler(lambda e: received.append(e))

        event = PolicyEvent(event_type=PolicyEventType.DECISION)
        handler.handle(event)

        assert len(received) == 1
        assert received[0] == event


class TestFilteredEventHandler:
    """Tests for FilteredEventHandler."""

    def test_filter_by_event_type(self) -> None:
        received: List[PolicyEvent] = []
        inner = CallbackEventHandler(lambda e: received.append(e))
        handler = FilteredEventHandler(
            inner,
            event_types={PolicyEventType.VIOLATION},
        )

        # Should be filtered out
        handler.handle(PolicyEvent(event_type=PolicyEventType.DECISION))
        assert len(received) == 0

        # Should pass through
        handler.handle(PolicyEvent(event_type=PolicyEventType.VIOLATION))
        assert len(received) == 1

    def test_filter_by_namespace(self) -> None:
        received: List[PolicyEvent] = []
        inner = CallbackEventHandler(lambda e: received.append(e))
        handler = FilteredEventHandler(
            inner,
            namespaces={"production"},
        )

        # Should be filtered out
        handler.handle(PolicyEvent(event_type=PolicyEventType.DECISION, namespace="dev"))
        assert len(received) == 0

        # Should pass through
        handler.handle(PolicyEvent(event_type=PolicyEventType.DECISION, namespace="production"))
        assert len(received) == 1

    def test_filter_by_min_severity(self) -> None:
        received: List[PolicyEvent] = []
        inner = CallbackEventHandler(lambda e: received.append(e))
        handler = FilteredEventHandler(
            inner,
            min_severity=PolicyResult.WARN,
        )

        # ALLOW should be filtered out (below min severity)
        handler.handle(PolicyEvent(
            event_type=PolicyEventType.DECISION,
            result=PolicyResult.ALLOW,
        ))
        assert len(received) == 0

        # WARN should pass through
        handler.handle(PolicyEvent(
            event_type=PolicyEventType.WARNING,
            result=PolicyResult.WARN,
        ))
        assert len(received) == 1

        # DENY should pass through (above min severity)
        handler.handle(PolicyEvent(
            event_type=PolicyEventType.VIOLATION,
            result=PolicyResult.DENY,
        ))
        assert len(received) == 2


class TestFileEventHandler:
    """Tests for FileEventHandler."""

    def test_write_events_to_file(self) -> None:
        with tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=False) as f:
            temp_path = f.name

        try:
            handler = FileEventHandler(Path(temp_path))

            event1 = PolicyEvent(
                event_type=PolicyEventType.DECISION,
                policy_name="test",
            )
            event2 = PolicyEvent(
                event_type=PolicyEventType.VIOLATION,
                policy_name="test",
            )

            handler.handle(event1)
            handler.handle(event2)
            handler.close()

            # Read and verify
            with open(temp_path, "r") as f:
                lines = f.readlines()

            assert len(lines) == 2
            data1 = json.loads(lines[0])
            assert data1["event_type"] == "decision"

            data2 = json.loads(lines[1])
            assert data2["event_type"] == "violation"
        finally:
            Path(temp_path).unlink(missing_ok=True)


class TestLoggingEventHandler:
    """Tests for LoggingEventHandler."""

    def test_logs_events(self, caplog: pytest.LogCaptureFixture) -> None:
        import logging
        # LoggingEventHandler uses "hieromem.policy" logger, set level on that specific logger
        caplog.set_level(logging.DEBUG, logger="hieromem.policy")

        handler = LoggingEventHandler()
        event = PolicyEvent(
            event_type=PolicyEventType.DECISION,
            policy_name="test-policy",
            result=PolicyResult.ALLOW,
        )
        handler.handle(event)

        assert "test-policy" in caplog.text or "decision" in caplog.text.lower()


class TestDefaultEmitter:
    """Tests for default emitter functions."""

    def test_get_default_emitter(self) -> None:
        emitter = get_default_emitter()
        assert isinstance(emitter, PolicyEventEmitter)

        # Same instance returned
        emitter2 = get_default_emitter()
        assert emitter is emitter2

    def test_setup_default_logging(self) -> None:
        emitter = get_default_emitter()
        initial_count = len(emitter._handlers)

        setup_default_logging()

        # Should have added a handler
        assert len(emitter._handlers) >= initial_count
