import os

import pytest

from logos_adk.agent import Agent
from logos_adk.agent_registry import agent_class_registry, agent_factory_registry
from logos_adk.errors import ConfigError
from logos_adk.event_bus import PostgreSQLEventBus
from logos_adk.providers.mock import MockProvider
from logos_adk.subscription_registry import subscription_handler, subscription_handler_registry
from logos_adk.team import Team
from logos_adk.team_config_parser import load_team_config, load_team_from_config, parse_team_config_data
from logos_adk.types import Response, Usage


FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures")


@pytest.fixture(autouse=True)
def _clear_subscription_handler_registry():
    subscription_handler_registry.clear()
    agent_class_registry.clear()
    agent_factory_registry.clear()
    yield
    subscription_handler_registry.clear()
    agent_class_registry.clear()
    agent_factory_registry.clear()


class DeclarativeSEOAgent(Agent):
    async def transform_event(self, event):
        return {"optimized": f"class::{event.payload['prompt']}"}


def declarative_seo_factory(config):
    return DeclarativeSEOAgent(config.name)


class TestParseTeamConfigData:
    def test_minimal_team(self):
        data = {
            "name": "t",
            "type": "team",
            "agents": [{"name": "a", "model": "mock"}],
        }
        cfg = parse_team_config_data(data)
        assert cfg.name == "t"
        assert len(cfg.agents) == 1

    def test_missing_name_raises(self):
        with pytest.raises(ConfigError, match="name"):
            parse_team_config_data({"type": "team", "agents": []})

    def test_missing_type_raises(self):
        with pytest.raises(ConfigError, match="type.*team"):
            parse_team_config_data({"name": "t", "agents": []})

    def test_wrong_type_raises(self):
        with pytest.raises(ConfigError, match="type.*team"):
            parse_team_config_data({"name": "t", "type": "agent", "agents": []})

    def test_orchestrator_parsed(self):
        data = {
            "name": "t",
            "type": "team",
            "orchestrator": "leader",
            "agents": [{"name": "leader", "model": "mock"}],
        }
        cfg = parse_team_config_data(data)
        assert cfg.orchestrator == "leader"

    def test_max_rounds_parsed(self):
        data = {"name": "t", "type": "team", "max_rounds": 20, "agents": []}
        cfg = parse_team_config_data(data)
        assert cfg.max_rounds == 20

    def test_supervisor_parsed(self):
        data = {
            "name": "t",
            "type": "team",
            "supervisor": {"on_failure": "skip", "max_retries": 5},
            "agents": [],
        }
        cfg = parse_team_config_data(data)
        assert cfg.supervisor is not None
        assert cfg.supervisor.on_failure == "skip"
        assert cfg.supervisor.max_retries == 5

    def test_topology_mesh_parsed(self):
        data = {"name": "t", "type": "team", "topology": "mesh", "agents": []}
        cfg = parse_team_config_data(data)
        assert cfg.topology == "mesh"

    def test_topology_dict_parsed(self):
        data = {
            "name": "t",
            "type": "team",
            "topology": {"a": ["b"], "b": []},
            "agents": [],
        }
        cfg = parse_team_config_data(data)
        assert cfg.topology == {"a": ["b"], "b": []}

    def test_process_and_manager_role_parsed(self):
        data = {
            "name": "t",
            "type": "team",
            "process": "hierarchical",
            "manager_role": "Manager",
            "agents": [],
        }
        cfg = parse_team_config_data(data)
        assert cfg.process == "hierarchical"
        assert cfg.manager_role == "Manager"

    def test_event_bus_topic_policy_and_subscriptions_parsed(self):
        data = {
            "name": "t",
            "type": "team",
            "agents": [{"name": "seo", "model": "mock"}],
            "event_bus": {"backend": "sqlite", "path": ".logos_adk/event_bus.db"},
            "topic_policy": {"seo": ["need_seo", "seo_done"]},
            "subscriptions": [
                {"agent": "seo", "topic": "need_seo", "publish_to": "seo_done"}
            ],
        }

        cfg = parse_team_config_data(data)

        assert cfg.event_bus is not None
        assert cfg.event_bus.backend == "sqlite"
        assert cfg.event_bus.path == ".logos_adk/event_bus.db"
        assert cfg.topic_policy == {"seo": ["need_seo", "seo_done"]}
        assert len(cfg.subscriptions) == 1
        assert cfg.subscriptions[0].agent_name == "seo"
        assert cfg.subscriptions[0].topic == "need_seo"
        assert cfg.subscriptions[0].publish_to == "seo_done"

    def test_event_bus_sqlite_requires_path(self):
        data = {
            "name": "t",
            "type": "team",
            "agents": [],
            "event_bus": {"backend": "sqlite"},
        }
        with pytest.raises(ConfigError, match="event_bus.path"):
            parse_team_config_data(data)

    def test_event_bus_postgresql_parsed(self):
        data = {
            "name": "t",
            "type": "team",
            "agents": [],
            "event_bus": {"backend": "postgres", "dsn": "postgresql://db/logos"},
        }

        cfg = parse_team_config_data(data)

        assert cfg.event_bus is not None
        assert cfg.event_bus.backend == "postgresql"
        assert cfg.event_bus.dsn == "postgresql://db/logos"

    def test_team_builds_postgresql_event_bus(self):
        cfg = parse_team_config_data(
            {
                "name": "t",
                "type": "team",
                "agents": [],
                "event_bus": {"backend": "postgresql", "dsn": "postgresql://db/logos"},
            }
        )

        team = Team.from_definition(cfg)

        assert isinstance(team.event_bus(), PostgreSQLEventBus)
        assert team.event_bus().dsn == "postgresql://db/logos"

    def test_subscription_unknown_agent_raises(self):
        data = {
            "name": "t",
            "type": "team",
            "agents": [{"name": "seo", "model": "mock"}],
            "subscriptions": [
                {"agent": "writer", "topic": "need_copy"}
            ],
        }
        with pytest.raises(ConfigError, match="unknown agent"):
            parse_team_config_data(data)

    def test_unknown_fields_raises(self):
        data = {"name": "t", "type": "team", "agents": [], "bogus": True}
        with pytest.raises(ConfigError, match="Unknown"):
            parse_team_config_data(data)

    def test_agents_are_agent_configs(self):
        data = {
            "name": "t",
            "type": "team",
            "agents": [
                {"name": "a", "model": "mock", "system_prompt": "be helpful"},
            ],
        }
        cfg = parse_team_config_data(data)
        assert cfg.agents[0].name == "a"
        assert cfg.agents[0].system_prompt == "be helpful"


class TestLoadTeamConfig:
    def test_load_hierarchical(self):
        cfg = load_team_config(os.path.join(FIXTURES, "team_hierarchical.yaml"))
        assert cfg.name == "content_team"
        assert cfg.orchestrator == "researcher"
        assert cfg.max_rounds == 15
        assert len(cfg.agents) == 3

    def test_load_mesh(self):
        cfg = load_team_config(os.path.join(FIXTURES, "team_mesh.yaml"))
        assert cfg.topology == "mesh"

    def test_load_pipeline(self):
        cfg = load_team_config(os.path.join(FIXTURES, "team_pipeline.yaml"))
        assert isinstance(cfg.topology, dict)
        assert cfg.topology["intake"] == ["processor"]

    def test_load_bad_orchestrator(self):
        """Config parses fine; validation happens at Team construction."""
        cfg = load_team_config(os.path.join(FIXTURES, "team_bad_orchestrator.yaml"))
        assert cfg.orchestrator == "nonexistent"

    def test_load_event_bus_fixture(self):
        cfg = load_team_config(os.path.join(FIXTURES, "team_event_bus.yaml"))
        assert cfg.event_bus is not None
        assert cfg.event_bus.backend == "memory"
        assert cfg.topic_policy == {"seo": ["need_seo", "seo_done"]}
        assert len(cfg.subscriptions) == 1

    @pytest.mark.asyncio
    async def test_load_team_from_config_builds_runtime_with_subscriptions(self):
        team = load_team_from_config(os.path.join(FIXTURES, "team_event_bus.yaml"))

        assert team.get_agent("seo") is not None
        assert len(team._topic_subscriptions) == 2
        assert any(item.topic == "__scheduled_task__:seo" for item in team._topic_subscriptions)
        assert any(item.topic == "need_seo" for item in team._topic_subscriptions)

        seo = team.get_agent("seo")
        seo.model(
            MockProvider(
                responses=[
                    Response(content="optimized-from-config", tool_calls=[], usage=Usage(1, 1), raw={})
                ]
            )
        )
        seo.publish_event(
            "need_seo",
            {"prompt": "summer sale"},
            correlation_id="corr-config",
            run_id="run-config",
        )

        response = await team.run_rounds()

        assert response.content == "optimized-from-config"
        events = team.event_bus().list_events(
            topic="seo_done",
            correlation_id="corr-config",
            run_id="run-config",
        )
        assert len(events) == 1
        assert events[0].payload["content"] == "optimized-from-config"

    @pytest.mark.asyncio
    async def test_load_team_from_config_resolves_registry_subscription_handler(self, tmp_path):
        @subscription_handler("seo_transform")
        async def seo_transform(event):
            return {"optimized": event.payload["prompt"].upper()}

        config_path = tmp_path / "team_registry.yaml"
        config_path.write_text(
            """
name: registry_team
type: team
event_bus:
  backend: memory
topic_policy:
  seo:
    - need_seo
    - seo_done
agents:
  - name: seo
    model: mock
subscriptions:
  - agent: seo
    topic: need_seo
    handler: seo_transform
    publish_to: seo_done
""".strip()
        )

        team = load_team_from_config(str(config_path))
        seo = team.get_agent("seo")
        seo.publish_event(
            "need_seo",
            {"prompt": "summer sale"},
            correlation_id="corr-registry",
            run_id="run-registry",
        )

        response = await team.run_rounds()

        assert "\"SUMMER SALE\"" in response.content
        events = team.event_bus().list_events(
            topic="seo_done",
            correlation_id="corr-registry",
            run_id="run-registry",
        )
        assert len(events) == 1
        assert events[0].payload == {"optimized": "SUMMER SALE"}

    @pytest.mark.asyncio
    async def test_load_team_from_config_resolves_import_path_subscription_handler(self, tmp_path):
        config_path = tmp_path / "team_import_path.yaml"
        config_path.write_text(
            """
name: import_team
type: team
event_bus:
  backend: memory
topic_policy:
  seo:
    - need_seo
    - seo_done
agents:
  - name: seo
    model: mock
subscriptions:
  - agent: seo
    topic: need_seo
    handler: tests.test_team_config.import_path_handler
    publish_to: seo_done
""".strip()
        )

        team = load_team_from_config(str(config_path))
        seo = team.get_agent("seo")
        seo.publish_event(
            "need_seo",
            {"prompt": "summer sale"},
            correlation_id="corr-import",
            run_id="run-import",
        )

        response = await team.run_rounds()

        assert "\"import::summer sale\"" in response.content
        events = team.event_bus().list_events(
            topic="seo_done",
            correlation_id="corr-import",
            run_id="run-import",
        )
        assert len(events) == 1
        assert events[0].payload == {"optimized": "import::summer sale"}

    @pytest.mark.asyncio
    async def test_load_team_from_config_resolves_custom_agent_class(self, tmp_path):
        config_path = tmp_path / "team_custom_agent.yaml"
        config_path.write_text(
            """
name: custom_agent_team
type: team
event_bus:
  backend: memory
topic_policy:
  seo:
    - need_seo
    - seo_done
agents:
  - name: seo
    agent_class: tests.test_team_config.DeclarativeSEOAgent
    model: mock
subscriptions:
  - agent: seo
    topic: need_seo
    handler: transform_event
    publish_to: seo_done
""".strip()
        )

        team = load_team_from_config(str(config_path))
        seo = team.get_agent("seo")

        assert isinstance(seo, DeclarativeSEOAgent)

        seo.publish_event(
            "need_seo",
            {"prompt": "summer sale"},
            correlation_id="corr-custom-agent",
            run_id="run-custom-agent",
        )

        response = await team.run_rounds()

        assert "\"class::summer sale\"" in response.content
        events = team.event_bus().list_events(
            topic="seo_done",
            correlation_id="corr-custom-agent",
            run_id="run-custom-agent",
        )
        assert len(events) == 1
        assert events[0].payload == {"optimized": "class::summer sale"}

    @pytest.mark.asyncio
    async def test_load_team_from_config_resolves_custom_agent_factory(self, tmp_path):
        config_path = tmp_path / "team_custom_factory.yaml"
        config_path.write_text(
            """
name: custom_factory_team
type: team
event_bus:
  backend: memory
topic_policy:
  seo:
    - need_seo
    - seo_done
agents:
  - name: seo
    agent_factory: tests.test_team_config.declarative_seo_factory
    model: mock
subscriptions:
  - agent: seo
    topic: need_seo
    handler: transform_event
    publish_to: seo_done
""".strip()
        )

        team = load_team_from_config(str(config_path))
        seo = team.get_agent("seo")

        assert isinstance(seo, DeclarativeSEOAgent)

        seo.publish_event(
            "need_seo",
            {"prompt": "summer sale"},
            correlation_id="corr-custom-factory",
            run_id="run-custom-factory",
        )

        response = await team.run_rounds()

        assert "\"class::summer sale\"" in response.content
        events = team.event_bus().list_events(
            topic="seo_done",
            correlation_id="corr-custom-factory",
            run_id="run-custom-factory",
        )
        assert len(events) == 1
        assert events[0].payload == {"optimized": "class::summer sale"}


async def import_path_handler(event):
    return {"optimized": f"import::{event.payload['prompt']}"}
