"""Тесты для SchemaValidator."""

import pytest

from ui_router.schema import (
    UIRouter,
    Scene,
    Handler,
    GlobalHandler,
    ActionType,
    EventType,
    Filter,
    MessageContent,
    Keyboard,
    Button,
    EventDefinition,
    EventHandler,
    EventSourceType,
    GotoSceneAction,
    BackAction,
    SendMessageAction,
)
from ui_router.validator import (
    SchemaValidator,
    ValidationSeverity,
    ValidationCategory,
)


def _make_scene(
    scene_id: str,
    *,
    handlers: list | None = None,
    on_enter: list | None = None,
    keyboard: Keyboard | None = None,
    parent_scene: str | None = None,
    allowed_scenes: list[str] | None = None,
) -> Scene:
    return Scene(
        id=scene_id,
        name=scene_id,
        handlers=handlers or [],
        on_enter=on_enter or [],
        default_keyboard=keyboard,
        parent_scene=parent_scene,
        allowed_scenes=allowed_scenes or [],
    )


def _goto(scene_id: str) -> GotoSceneAction:
    return GotoSceneAction(scene_id=scene_id)


def _back() -> BackAction:
    return BackAction()


def _send(text: str = "ok") -> SendMessageAction:
    return SendMessageAction(content=MessageContent(text=text))


def _handler(name: str, actions: list, *, event_type: EventType = EventType.CALLBACK) -> Handler:
    return Handler(
        name=name,
        event_type=event_type,
        actions=actions,
        filters=[Filter(type="text", text="x")] if event_type == EventType.MESSAGE else [],
    )


def _make_schema(
    scenes: list[Scene],
    *,
    initial_scene: str = "main",
    global_handlers: list | None = None,
    events: list | None = None,
    event_handlers: list | None = None,
) -> UIRouter:
    return UIRouter(
        name="test",
        scenes=scenes,
        initial_scene=initial_scene,
        global_handlers=global_handlers or [],
        events=events or [],
        event_handlers=event_handlers or [],
    )


@pytest.fixture
def validator():
    return SchemaValidator()


class TestValidSchema:
    def test_minimal_valid_schema(self, validator):
        schema = _make_schema([_make_scene("main")])
        report = validator.validate(schema)
        # main — тупик, но ошибок быть не должно
        assert report.is_valid
        assert len(report.errors) == 0

    def test_two_scenes_linked(self, validator):
        schema = _make_schema([
            _make_scene("main", handlers=[_handler("go", [_goto("second")])]),
            _make_scene("second", handlers=[_handler("back", [_back()])]),
        ])
        report = validator.validate(schema)
        assert report.is_valid
        assert len(report.warnings) == 0


class TestBrokenSceneRef:
    def test_goto_nonexistent_scene(self, validator):
        schema = _make_schema([
            _make_scene("main", handlers=[_handler("go", [_goto("missing")])]),
        ])
        report = validator.validate(schema)
        assert not report.is_valid
        assert any(i.category == ValidationCategory.BROKEN_SCENE_REF for i in report.errors)

    def test_goto_in_on_enter(self, validator):
        schema = _make_schema([
            _make_scene("main", on_enter=[_goto("missing")]),
        ])
        report = validator.validate(schema)
        assert not report.is_valid
        assert any(i.category == ValidationCategory.BROKEN_SCENE_REF for i in report.errors)


class TestBrokenCallbackRef:
    def test_callback_action_missing_handler(self, validator):
        kb = Keyboard(buttons=[[Button(text="Go", callback_action="nonexistent")]])
        schema = _make_schema([
            _make_scene("main", keyboard=kb),
        ])
        report = validator.validate(schema)
        assert not report.is_valid
        assert any(i.category == ValidationCategory.BROKEN_CALLBACK_REF for i in report.errors)

    def test_callback_action_exists(self, validator):
        kb = Keyboard(buttons=[[Button(text="Go", callback_action="act")]])
        schema = _make_schema([
            _make_scene(
                "main",
                keyboard=kb,
                handlers=[_handler("act", [_send()])],
            ),
        ])
        report = validator.validate(schema)
        # Нет ошибок по callback
        assert not any(i.category == ValidationCategory.BROKEN_CALLBACK_REF for i in report.issues)


class TestBrokenGlobalRef:
    def test_callback_global_missing(self, validator):
        kb = Keyboard(buttons=[[Button(text="Go", callback_global="missing_global")]])
        schema = _make_schema([
            _make_scene("main", keyboard=kb),
        ])
        report = validator.validate(schema)
        assert not report.is_valid
        assert any(i.category == ValidationCategory.BROKEN_GLOBAL_REF for i in report.errors)

    def test_callback_global_exists(self, validator):
        kb = Keyboard(buttons=[[Button(text="Go", callback_global="gh1")]])
        gh = GlobalHandler(
            name="gh1",
            event_type=EventType.CALLBACK,
            filters=[],
            actions=[_send()],
        )
        schema = _make_schema([_make_scene("main", keyboard=kb)], global_handlers=[gh])
        report = validator.validate(schema)
        assert not any(i.category == ValidationCategory.BROKEN_GLOBAL_REF for i in report.issues)


class TestBrokenParentRef:
    def test_parent_scene_missing(self, validator):
        schema = _make_schema([
            _make_scene("main", parent_scene="nonexistent"),
        ])
        report = validator.validate(schema)
        assert not report.is_valid
        assert any(i.category == ValidationCategory.BROKEN_PARENT_REF for i in report.errors)


class TestBrokenAllowedRef:
    def test_allowed_scene_missing(self, validator):
        schema = _make_schema([
            _make_scene("main", allowed_scenes=["nonexistent"]),
        ])
        report = validator.validate(schema)
        assert not report.is_valid
        assert any(i.category == ValidationCategory.BROKEN_ALLOWED_REF for i in report.errors)


class TestUnreachableScene:
    def test_unreachable_scene_warning(self, validator):
        schema = _make_schema([
            _make_scene("main"),
            _make_scene("orphan"),
        ])
        report = validator.validate(schema)
        assert report.is_valid  # WARNING, не ERROR
        assert any(
            i.category == ValidationCategory.UNREACHABLE_SCENE and "orphan" in i.message for i in report.warnings
        )

    def test_reachable_via_global_handler(self, validator):
        gh = GlobalHandler(
            name="go_orphan",
            event_type=EventType.CALLBACK,
            filters=[],
            actions=[_goto("second")],
        )
        schema = _make_schema(
            [_make_scene("main"), _make_scene("second")],
            global_handlers=[gh],
        )
        report = validator.validate(schema)
        assert not any(i.category == ValidationCategory.UNREACHABLE_SCENE for i in report.issues)


class TestDeadEndScene:
    def test_dead_end_scene_info(self, validator):
        schema = _make_schema([
            _make_scene("main", handlers=[_handler("act", [_send()])]),
        ])
        report = validator.validate(schema)
        assert any(i.category == ValidationCategory.DEAD_END_SCENE and "main" in i.message for i in report.infos)

    def test_scene_with_navigation_not_dead_end(self, validator):
        schema = _make_schema([
            _make_scene("main", handlers=[_handler("go", [_goto("main")])]),
        ])
        report = validator.validate(schema)
        assert not any(i.category == ValidationCategory.DEAD_END_SCENE for i in report.issues)


class TestUnusedGlobalHandler:
    def test_unused_callback_global_handler(self, validator):
        gh = GlobalHandler(
            name="unused_gh",
            event_type=EventType.CALLBACK,
            filters=[],
            actions=[_send()],
        )
        schema = _make_schema([_make_scene("main")], global_handlers=[gh])
        report = validator.validate(schema)
        assert any(
            i.category == ValidationCategory.UNUSED_GLOBAL_HANDLER and "unused_gh" in i.message for i in report.infos
        )

    def test_message_global_handler_not_flagged(self, validator):
        """MESSAGE глобальные хендлеры (типа /start) не флагуются как unused."""
        gh = GlobalHandler(
            name="start_cmd",
            event_type=EventType.MESSAGE,
            filters=[Filter(type="command", commands=["start"])],
            actions=[_send()],
        )
        schema = _make_schema([_make_scene("main")], global_handlers=[gh])
        report = validator.validate(schema)
        assert not any(i.category == ValidationCategory.UNUSED_GLOBAL_HANDLER for i in report.issues)


class TestBrokenEventRef:
    def test_event_handler_references_undefined_event(self, validator):
        eh = EventHandler(event_name="undefined_event", actions=[_send()])
        schema = _make_schema([_make_scene("main")], event_handlers=[eh])
        report = validator.validate(schema)
        assert not report.is_valid
        assert any(i.category == ValidationCategory.BROKEN_EVENT_REF for i in report.errors)

    def test_event_handler_references_defined_event(self, validator):
        ev = EventDefinition(name="my_event", source_type=EventSourceType.INTERNAL)
        eh = EventHandler(event_name="my_event", actions=[_send()])
        schema = _make_schema([_make_scene("main")], events=[ev], event_handlers=[eh])
        report = validator.validate(schema)
        assert not any(i.category == ValidationCategory.BROKEN_EVENT_REF for i in report.issues)


class TestReportProperties:
    def test_is_valid_with_no_errors(self, validator):
        schema = _make_schema([_make_scene("main")])
        report = validator.validate(schema)
        assert report.is_valid

    def test_is_valid_false_with_errors(self, validator):
        schema = _make_schema([
            _make_scene("main", handlers=[_handler("go", [_goto("missing")])]),
        ])
        report = validator.validate(schema)
        assert not report.is_valid
