"""Contract tests for plugin contract.

These tests verify that plugin modules satisfy the architectural requirements:
- Must export PLUGIN_MANIFEST: PluginManifest
- Must export MODEL_REQUIREMENTS: list[ModelManifest]
- Must export create_engine: Callable[[HostContext, Path | None, EngineConfig], Engine]
- create_engine() must be atomic
"""

import logging
from pathlib import Path
from typing import Any

import pytest

from abogen.tts_plugin.engine import Engine
from abogen.tts_plugin.host_context import HostContext
from abogen.tts_plugin.manifest import EngineManifest, ModelManifest, PluginManifest
from abogen.tts_plugin.plugin import Plugin
from abogen.tts_plugin.types import EngineConfig

from .conftest import FakeEngine


class FakePluginModule:
    """Stub plugin module that satisfies the plugin contract."""

    PLUGIN_MANIFEST = PluginManifest(
        id="fake-plugin",
        name="Fake Plugin",
        version="1.0.0",
        api_version="1.0",
        description="A fake plugin for testing",
        author="Test Author",
        capabilities=(),
        engine=EngineManifest(),
    )

    MODEL_REQUIREMENTS: list[ModelManifest] = []

    @staticmethod
    def create_engine(
        context: HostContext,
        model_path: Path | None,
        config: EngineConfig,
    ) -> Engine:
        return FakeEngine()


class TestPluginProtocolContract:
    """Contract tests for the Plugin protocol."""

    def test_plugin_is_protocol(self) -> None:
        assert hasattr(Plugin, "__protocol_attrs__")


class TestPluginExportsContract:
    """Contract tests for required plugin exports."""

    def test_plugin_has_plugin_manifest(self) -> None:
        """Architecture spec: Plugin must export PLUGIN_MANIFEST."""
        assert hasattr(FakePluginModule, "PLUGIN_MANIFEST")
        assert isinstance(FakePluginModule.PLUGIN_MANIFEST, PluginManifest)

    def test_plugin_has_model_requirements(self) -> None:
        """Architecture spec: Plugin must export MODEL_REQUIREMENTS."""
        assert hasattr(FakePluginModule, "MODEL_REQUIREMENTS")
        assert isinstance(FakePluginModule.MODEL_REQUIREMENTS, list)

    def test_plugin_has_create_engine(self) -> None:
        """Architecture spec: Plugin must export create_engine."""
        assert hasattr(FakePluginModule, "create_engine")
        assert callable(FakePluginModule.create_engine)

    def test_plugin_manifest_required_fields(self) -> None:
        """Architecture spec: PluginManifest has required fields."""
        manifest = FakePluginModule.PLUGIN_MANIFEST
        assert manifest.id
        assert manifest.name
        assert manifest.version
        assert manifest.api_version
        assert manifest.description
        assert manifest.author

    def test_plugin_manifest_capabilities_is_tuple(self) -> None:
        manifest = FakePluginModule.PLUGIN_MANIFEST
        assert isinstance(manifest.capabilities, tuple)


class TestCreateEngineContract:
    """Contract tests for create_engine() function."""

    def test_create_engine_returns_engine(self) -> None:
        """Architecture spec: create_engine() returns Engine."""
        ctx = HostContext(
            config_dir=Path("/tmp/test"),
            logger=logging.getLogger("test"),
            http_client=type("FakeClient", (), {"get": lambda self, **kw: None, "post": lambda self, **kw: None})(),
        )
        engine = FakePluginModule.create_engine(ctx, None, EngineConfig())
        assert isinstance(engine, Engine)

    def test_create_engine_atomic(self) -> None:
        """Architecture spec: create_engine() is atomic (all-or-nothing)."""
        ctx = HostContext(
            config_dir=Path("/tmp/test"),
            logger=logging.getLogger("test"),
            http_client=type("FakeClient", (), {"get": lambda self, **kw: None, "post": lambda self, **kw: None})(),
        )
        engine = FakePluginModule.create_engine(ctx, None, EngineConfig())
        assert isinstance(engine, Engine)
        engine.dispose()

    def test_create_engine_with_none_model_path(self) -> None:
        """Architecture spec: model_path can be None for cloud/no-model engines."""
        ctx = HostContext(
            config_dir=Path("/tmp/test"),
            logger=logging.getLogger("test"),
            http_client=type("FakeClient", (), {"get": lambda self, **kw: None, "post": lambda self, **kw: None})(),
        )
        engine = FakePluginModule.create_engine(ctx, None, EngineConfig())
        assert isinstance(engine, Engine)
        engine.dispose()

    def test_create_engine_with_model_path(self) -> None:
        """Architecture spec: model_path is Path | None."""
        ctx = HostContext(
            config_dir=Path("/tmp/test"),
            logger=logging.getLogger("test"),
            http_client=type("FakeClient", (), {"get": lambda self, **kw: None, "post": lambda self, **kw: None})(),
        )
        engine = FakePluginModule.create_engine(ctx, Path("/models/test"), EngineConfig())
        assert isinstance(engine, Engine)
        engine.dispose()


class TestModelRequirementsContract:
    """Contract tests for MODEL_REQUIREMENTS."""

    def test_model_requirements_is_list(self) -> None:
        assert isinstance(FakePluginModule.MODEL_REQUIREMENTS, list)

    def test_model_requirements_contains_model_manifests(self) -> None:
        """If non-empty, each item must be a ModelManifest."""
        for req in FakePluginModule.MODEL_REQUIREMENTS:
            assert isinstance(req, ModelManifest)
