"""
Tests for Automation base class.
"""

from typing import Annotated, Any

import pytest
from langgraph.graph import StateGraph

from torivers_sdk.automation.base import Automation
from torivers_sdk.automation.metadata import AutomationMetadata
from torivers_sdk.automation.state import BaseState, last_value


class TestAutomationAbstract:
    """Test suite for Automation abstract class."""

    def test_cannot_instantiate_abstract(self) -> None:
        """Test that Automation cannot be instantiated directly."""
        with pytest.raises(TypeError, match="abstract"):
            Automation()  # type: ignore

    def test_must_implement_metadata(self) -> None:
        """Test that subclass must implement metadata()."""

        class IncompleteAutomation(Automation):  # type: ignore
            def get_state_class(self):
                return BaseState

            def build_graph(self):
                return StateGraph(BaseState)

        with pytest.raises(TypeError, match="abstract"):
            IncompleteAutomation()

    def test_must_implement_get_state_class(self) -> None:
        """Test that subclass must implement get_state_class()."""

        class IncompleteAutomation(Automation):  # type: ignore
            def metadata(self):
                return AutomationMetadata(
                    name="test-auto",
                    title="Test",
                    version="1.0.0",
                    description="Test description",
                    base_price=1.0,
                )

            def build_graph(self):
                return StateGraph(BaseState)

        with pytest.raises(TypeError, match="abstract"):
            IncompleteAutomation()

    def test_must_implement_build_graph(self) -> None:
        """Test that subclass must implement build_graph()."""

        class IncompleteAutomation(Automation):  # type: ignore
            def metadata(self):
                return AutomationMetadata(
                    name="test-auto",
                    title="Test",
                    version="1.0.0",
                    description="Test description",
                    base_price=1.0,
                )

            def get_state_class(self):
                return BaseState

        with pytest.raises(TypeError, match="abstract"):
            IncompleteAutomation()


class TestCompleteAutomation:
    """Test suite for a complete Automation implementation."""

    def test_complete_automation(self) -> None:
        """Test that a complete implementation works."""

        class MyState(BaseState):
            result: Annotated[str, last_value]

        class MyAutomation(Automation[MyState]):
            def metadata(self) -> AutomationMetadata:
                return AutomationMetadata(
                    name="my-automation",
                    title="My Automation",
                    version="1.0.0",
                    description="A test automation",
                    base_price=1.0,
                )

            def get_state_class(self) -> type[MyState]:
                return MyState

            def build_graph(self) -> StateGraph:
                graph = StateGraph(MyState)
                graph.add_node("process", self.process)
                graph.set_entry_point("process")
                return graph

            def process(self, state: MyState) -> dict[str, Any]:
                return {"result": "done"}

        automation = MyAutomation()

        # Test metadata
        meta = automation.metadata()
        assert meta.name == "my-automation"
        assert meta.version == "1.0.0"

        # Test state class
        state_cls = automation.get_state_class()
        assert state_cls is MyState

        # Test graph building
        graph = automation.build_graph()
        assert isinstance(graph, StateGraph)

    def test_default_validate_input(self) -> None:
        """Test default validate_input returns True."""

        class MyState(BaseState):
            pass

        class MyAutomation(Automation[MyState]):
            def metadata(self) -> AutomationMetadata:
                return AutomationMetadata(
                    name="test-auto",
                    title="Test",
                    version="1.0.0",
                    description="Test description",
                    base_price=1.0,
                )

            def get_state_class(self) -> type[MyState]:
                return MyState

            def build_graph(self) -> StateGraph:
                return StateGraph(MyState)

        automation = MyAutomation()
        assert automation.validate_input({"foo": "bar"}) is True

    def test_default_credentials_empty(self) -> None:
        """Test default credential lists are empty."""

        class MyState(BaseState):
            pass

        class MyAutomation(Automation[MyState]):
            def metadata(self) -> AutomationMetadata:
                return AutomationMetadata(
                    name="test-auto",
                    title="Test",
                    version="1.0.0",
                    description="Test description",
                    base_price=1.0,
                )

            def get_state_class(self) -> type[MyState]:
                return MyState

            def build_graph(self) -> StateGraph:
                return StateGraph(MyState)

        automation = MyAutomation()
        assert automation.get_required_credentials() == []
        assert automation.get_optional_credentials() == []

    def test_custom_credentials(self) -> None:
        """Test overriding credential methods."""

        class MyState(BaseState):
            pass

        class MyAutomation(Automation[MyState]):
            def metadata(self) -> AutomationMetadata:
                return AutomationMetadata(
                    name="test-auto",
                    title="Test",
                    version="1.0.0",
                    description="Test description",
                    base_price=1.0,
                )

            def get_state_class(self) -> type[MyState]:
                return MyState

            def build_graph(self) -> StateGraph:
                return StateGraph(MyState)

            def get_required_credentials(self) -> list[str]:
                return ["gmail", "google_sheets"]

            def get_optional_credentials(self) -> list[str]:
                return ["slack"]

        automation = MyAutomation()
        assert automation.get_required_credentials() == ["gmail", "google_sheets"]
        assert automation.get_optional_credentials() == ["slack"]

    def test_lifecycle_hooks(self) -> None:
        """Test lifecycle hooks can be overridden."""
        startup_called = False
        shutdown_called = False
        error_handled = False

        class MyState(BaseState):
            pass

        class MyAutomation(Automation[MyState]):
            def metadata(self) -> AutomationMetadata:
                return AutomationMetadata(
                    name="test-auto",
                    title="Test",
                    version="1.0.0",
                    description="Test description",
                    base_price=1.0,
                )

            def get_state_class(self) -> type[MyState]:
                return MyState

            def build_graph(self) -> StateGraph:
                return StateGraph(MyState)

            def on_startup(self) -> None:
                nonlocal startup_called
                startup_called = True

            def on_shutdown(self) -> None:
                nonlocal shutdown_called
                shutdown_called = True

            def on_error(self, error: Exception) -> None:
                nonlocal error_handled
                error_handled = True

        automation = MyAutomation()

        automation.on_startup()
        assert startup_called

        automation.on_shutdown()
        assert shutdown_called

        automation.on_error(ValueError("test error"))
        assert error_handled

    @pytest.mark.asyncio
    async def test_async_lifecycle_hooks(self) -> None:
        """Test async lifecycle hooks can be overridden."""
        startup_async_called = False
        shutdown_async_called = False

        class MyState(BaseState):
            pass

        class MyAutomation(Automation[MyState]):
            def metadata(self) -> AutomationMetadata:
                return AutomationMetadata(
                    name="test-auto",
                    title="Test",
                    version="1.0.0",
                    description="Test description",
                    base_price=1.0,
                )

            def get_state_class(self) -> type[MyState]:
                return MyState

            def build_graph(self) -> StateGraph:
                return StateGraph(MyState)

            async def on_startup_async(self) -> None:
                nonlocal startup_async_called
                startup_async_called = True

            async def on_shutdown_async(self) -> None:
                nonlocal shutdown_async_called
                shutdown_async_called = True

        automation = MyAutomation()

        await automation.on_startup_async()
        assert startup_async_called

        await automation.on_shutdown_async()
        assert shutdown_async_called

    @pytest.mark.asyncio
    async def test_both_sync_and_async_hooks(self) -> None:
        """Test that both sync and async hooks can be used together."""
        call_order = []

        class MyState(BaseState):
            pass

        class MyAutomation(Automation[MyState]):
            def metadata(self) -> AutomationMetadata:
                return AutomationMetadata(
                    name="test-auto",
                    title="Test",
                    version="1.0.0",
                    description="Test description",
                    base_price=1.0,
                )

            def get_state_class(self) -> type[MyState]:
                return MyState

            def build_graph(self) -> StateGraph:
                return StateGraph(MyState)

            def on_startup(self) -> None:
                call_order.append("sync_startup")

            async def on_startup_async(self) -> None:
                call_order.append("async_startup")

            def on_shutdown(self) -> None:
                call_order.append("sync_shutdown")

            async def on_shutdown_async(self) -> None:
                call_order.append("async_shutdown")

        automation = MyAutomation()

        # Test startup hooks
        await automation.on_startup_async()
        automation.on_startup()
        assert call_order == ["async_startup", "sync_startup"]

        # Test shutdown hooks
        call_order.clear()
        automation.on_shutdown()
        await automation.on_shutdown_async()
        assert call_order == ["sync_shutdown", "async_shutdown"]


class TestLangGraphExports:
    """Test that langgraph symbols are properly exported."""

    def test_stategraph_import(self) -> None:
        """Test StateGraph can be imported from SDK."""
        from torivers_sdk import StateGraph

        assert StateGraph is not None

    def test_start_end_import(self) -> None:
        """Test START and END can be imported from SDK."""
        from torivers_sdk import END, START

        assert START is not None
        assert END is not None

    def test_automation_module_exports(self) -> None:
        """Test exports from automation module."""
        from torivers_sdk.automation import END, START, StateGraph

        assert StateGraph is not None
        assert START is not None
        assert END is not None
