# tests/tests_cli/test_strategy_registry.py
import logging
import unittest
from typing import Any

logger = logging.getLogger(__name__)


class TestGenerationContext(unittest.TestCase):
    """Tests for the GenerationContext dataclass."""

    def test_context_creation_with_all_fields(self) -> None:
        """Context can be created with all required fields."""
        from src.xstate_statemachine.cli.strategies.base import (
            GenerationContext,
        )

        ctx = GenerationContext(
            actions={"act1"},
            guards={"g1"},
            services={"svc1"},
            is_async=True,
            log=True,
            machine_name="test_machine",
            machine_id="testMachine",
            machine_names=["test_machine"],
            machine_ids=["testMachine"],
            file_count=2,
            configs=[{"id": "testMachine"}],
            json_filenames=["test.json"],
            hierarchy=False,
            sleep=True,
            sleep_time=2,
            loader=True,
            style="class",
        )
        self.assertEqual(ctx.machine_name, "test_machine")
        self.assertEqual(ctx.machine_id, "testMachine")
        self.assertTrue(ctx.is_async)
        self.assertEqual(ctx.style, "class")

    def test_context_style_defaults_to_none(self) -> None:
        """Style field defaults to None when not provided."""
        from src.xstate_statemachine.cli.strategies.base import (
            GenerationContext,
        )

        ctx = GenerationContext(
            actions=set(),
            guards=set(),
            services=set(),
            is_async=False,
            log=False,
            machine_name="m",
            machine_id="m",
            machine_names=["m"],
            machine_ids=["m"],
            file_count=1,
            configs=[{}],
            json_filenames=["m.json"],
            hierarchy=False,
            sleep=False,
            sleep_time=0,
            loader=False,
        )
        self.assertIsNone(ctx.style)


class TestBaseStrategy(unittest.TestCase):
    """Tests for the BaseStrategy abstract class."""

    def test_cannot_instantiate_base_strategy(self) -> None:
        """BaseStrategy is abstract and cannot be instantiated."""
        from src.xstate_statemachine.cli.strategies.base import (
            BaseStrategy,
        )

        with self.assertRaises(TypeError):
            BaseStrategy()  # type: ignore[abstract]

    def test_concrete_strategy_must_implement_methods(
        self,
    ) -> None:
        """A subclass missing methods cannot be instantiated."""
        from src.xstate_statemachine.cli.strategies.base import (
            BaseStrategy,
        )

        class Incomplete(BaseStrategy):
            pass

        with self.assertRaises(TypeError):
            Incomplete()  # type: ignore[abstract]

    def test_concrete_strategy_works(self) -> None:
        """A fully implemented subclass can be instantiated."""
        from src.xstate_statemachine.cli.strategies.base import (
            BaseStrategy,
            GenerationContext,
        )

        class DummyStrategy(BaseStrategy):
            @property
            def name(self) -> str:
                return "dummy"

            def generate_logic(self, ctx: GenerationContext) -> str:
                return "logic"

            def generate_runner(self, ctx: GenerationContext) -> str:
                return "runner"

        s = DummyStrategy()
        self.assertEqual(s.name, "dummy")


class TestSharedHelpers(unittest.TestCase):
    """Tests for the _shared.py helper functions."""

    def test_pascal_case_name(self) -> None:
        """snake_case converts to PascalCase."""
        from src.xstate_statemachine.cli.strategies._shared import (
            pascal_case_name,
        )

        self.assertEqual(pascal_case_name("traffic_light"), "TrafficLight")
        self.assertEqual(pascal_case_name("simple"), "Simple")
        self.assertEqual(pascal_case_name("my_cool_machine"), "MyCoolMachine")

    def test_generate_module_header(self) -> None:
        """Module header is a docstring with title."""
        from src.xstate_statemachine.cli.strategies._shared import (
            generate_module_header,
        )

        header = generate_module_header("Traffic Light")
        self.assertIn('"""', header)
        self.assertIn("Traffic Light", header)
        self.assertIn("generated by xsm CLI", header)

    def test_generate_section_header(self) -> None:
        """Section header contains title and separator."""
        from src.xstate_statemachine.cli.strategies._shared import (
            generate_section_header,
        )

        header = generate_section_header("Actions")
        self.assertIn("Actions", header)
        self.assertIn("---", header)

    def test_generate_logger_setup_enabled(self) -> None:
        """Logger setup includes getLogger when enabled."""
        from src.xstate_statemachine.cli.strategies._shared import (
            generate_logger_setup,
        )

        result = generate_logger_setup(True)
        self.assertIn("logging.getLogger(__name__)", result)

    def test_generate_logger_setup_disabled(self) -> None:
        """Logger setup returns empty when disabled."""
        from src.xstate_statemachine.cli.strategies._shared import (
            generate_logger_setup,
        )

        result = generate_logger_setup(False)
        self.assertEqual(result, "")

    def test_generate_action_docstring(self) -> None:
        """Action docstring includes Args section."""
        from src.xstate_statemachine.cli.strategies._shared import (
            generate_action_docstring,
        )

        doc = generate_action_docstring("myAction", "action")
        self.assertIn("myAction", doc)
        self.assertIn("Args:", doc)
        self.assertIn("interpreter:", doc)

    def test_generate_guard_docstring(self) -> None:
        """Guard docstring includes context and event args."""
        from src.xstate_statemachine.cli.strategies._shared import (
            generate_action_docstring,
        )

        doc = generate_action_docstring("isReady", "guard")
        self.assertIn("isReady", doc)
        self.assertIn("Args:", doc)
        self.assertIn("context:", doc)

    def test_generate_error_handling(self) -> None:
        """Error handling wraps body in try/except."""
        from src.xstate_statemachine.cli.strategies._shared import (
            generate_error_handling,
        )

        result = generate_error_handling(
            "myAction",
            "action",
            ["    # TODO: implement"],
            "    ",
        )
        self.assertIn("try:", result)
        self.assertIn("except Exception:", result)
        self.assertIn("logger.exception", result)
        self.assertIn("myAction", result)

    def test_generate_imports_pythonic(self) -> None:
        """Pythonic imports include State, action, guard, typing, logging."""
        from src.xstate_statemachine.cli.strategies._shared import (
            generate_imports,
        )

        result = generate_imports(
            is_async=False,
            services=set(),
            template_type="pythonic-class",
            log=True,
        )
        self.assertIn("from xstate_statemachine import", result)
        self.assertIn("State", result)
        self.assertIn("StateMachine", result)
        self.assertIn("import logging", result)
        self.assertIn("from typing import Any, Dict", result)

    def test_generate_imports_json(self) -> None:
        """JSON imports include Interpreter, Event, logging, etc."""
        from src.xstate_statemachine.cli.strategies._shared import (
            generate_imports,
        )

        result = generate_imports(
            is_async=True,
            services=set(),
            template_type="class-json",
            log=True,
        )
        self.assertIn("from xstate_statemachine import", result)
        self.assertIn("Interpreter", result)
        self.assertIn("Event", result)
        self.assertIn("import logging", result)
        self.assertNotIn("Awaitable", result)


class TestStrategyRegistry(unittest.TestCase):
    """Tests for the strategy registry and get_strategy()."""

    def test_safe_identifier_keyword(self) -> None:
        """Python keywords get underscore suffix."""
        from src.xstate_statemachine.cli.strategies._shared import (
            safe_identifier,
        )

        self.assertEqual(safe_identifier("class"), "class_")
        self.assertEqual(safe_identifier("return"), "return_")
        self.assertEqual(safe_identifier("import"), "import_")

    def test_safe_identifier_normal(self) -> None:
        """Normal names pass through unchanged."""
        from src.xstate_statemachine.cli.strategies._shared import (
            safe_identifier,
        )

        self.assertEqual(safe_identifier("green"), "green")
        self.assertEqual(safe_identifier("myState"), "myState")

    def test_safe_identifier_digit_prefix(self) -> None:
        """Names starting with digits get s_ prefix."""
        from src.xstate_statemachine.cli.strategies._shared import (
            safe_identifier,
        )

        self.assertEqual(safe_identifier("1state"), "s_1state")

    def test_safe_identifier_special_chars(self) -> None:
        """Special characters are replaced with underscores."""
        from src.xstate_statemachine.cli.strategies._shared import (
            safe_identifier,
        )

        self.assertEqual(safe_identifier("my-state"), "my_state")
        self.assertEqual(safe_identifier("state.name"), "state_name")

    def test_get_strategy_class_json(self) -> None:
        """class-json returns a ClassJsonStrategy."""
        from src.xstate_statemachine.cli.strategies import (
            get_strategy,
        )

        s = get_strategy("class-json")
        self.assertEqual(s.name, "class-json")

    def test_get_strategy_function_json(self) -> None:
        """function-json returns a FunctionJsonStrategy."""
        from src.xstate_statemachine.cli.strategies import (
            get_strategy,
        )

        s = get_strategy("function-json")
        self.assertEqual(s.name, "function-json")

    def test_get_strategy_pythonic_class(self) -> None:
        """pythonic-class returns a PythonicClassStrategy."""
        from src.xstate_statemachine.cli.strategies import (
            get_strategy,
        )

        s = get_strategy("pythonic-class")
        self.assertEqual(s.name, "pythonic-class")

    def test_get_strategy_pythonic_builder(self) -> None:
        """pythonic-builder returns correct strategy."""
        from src.xstate_statemachine.cli.strategies import (
            get_strategy,
        )

        s = get_strategy("pythonic-builder")
        self.assertEqual(s.name, "pythonic-builder")

    def test_get_strategy_pythonic_functional(self) -> None:
        """pythonic-functional returns correct strategy."""
        from src.xstate_statemachine.cli.strategies import (
            get_strategy,
        )

        s = get_strategy("pythonic-functional")
        self.assertEqual(s.name, "pythonic-functional")

    def test_get_strategy_unknown_raises(self) -> None:
        """Unknown template raises InvalidConfigError."""
        from src.xstate_statemachine.cli.strategies import (
            get_strategy,
        )
        from src.xstate_statemachine.exceptions import (
            InvalidConfigError,
        )

        with self.assertRaises(InvalidConfigError) as cm:
            get_strategy("nonexistent")
        self.assertIn("nonexistent", str(cm.exception))
        self.assertIn("Valid templates", str(cm.exception))
