"""Tests for Annie SDK Agent."""

from __future__ import annotations

from decimal import Decimal

import pytest

from annie_sdk import (
    Agent,
    AuthenticationError,
    ConnectorTable,
    ConnectorTableColumn,
    MockConnector,
)


# =========================================================================
# Fixtures
# =========================================================================


def _make_agent(mock_connector, api_key="test-key"):
    """Create an Agent with a mock connector (does NOT auto-connect)."""
    return Agent(connector=mock_connector, api_key=api_key)


# =========================================================================
# Initialization
# =========================================================================


class TestAgentInit:
    def test_requires_api_key(self, mock_connector, monkeypatch):
        monkeypatch.delenv("ANNIE_API_KEY", raising=False)
        with pytest.raises(AuthenticationError, match="API key is required"):
            Agent(connector=mock_connector)

    def test_api_key_from_env(self, mock_connector, monkeypatch):
        monkeypatch.setenv("ANNIE_API_KEY", "env-key")
        agent = Agent(connector=mock_connector)
        assert agent._api_key == "env-key"
        agent.close()

    def test_api_key_param_overrides_env(self, mock_connector, monkeypatch):
        monkeypatch.setenv("ANNIE_API_KEY", "env-key")
        agent = Agent(connector=mock_connector, api_key="param-key")
        assert agent._api_key == "param-key"
        agent.close()

    def test_custom_api_url(self, mock_connector):
        agent = Agent(
            connector=mock_connector,
            api_key="k",
            api_url="https://custom.api.com",
        )
        assert agent._api_url == "https://custom.api.com"
        agent.close()

    def test_api_url_strips_trailing_slash(self, mock_connector):
        agent = Agent(
            connector=mock_connector,
            api_key="k",
            api_url="https://api.example.com/",
        )
        assert agent._api_url == "https://api.example.com"
        agent.close()

    def test_context_manager(self, mock_connector):
        with Agent(connector=mock_connector, api_key="k") as agent:
            assert agent is not None
        # After exit, connector should be disconnected
        assert not mock_connector.is_connected()


# =========================================================================
# Agent.run() — error paths only (no real API)
# =========================================================================


class TestAgentRun:
    def test_empty_query(self, mock_connector):
        agent = _make_agent(mock_connector)
        response = agent.run("")
        assert not response.success
        assert response.error is not None
        assert "empty" in response.error.content.lower()
        agent.close()

    def test_whitespace_query(self, mock_connector):
        agent = _make_agent(mock_connector)
        response = agent.run("   ")
        assert not response.success
        agent.close()


# =========================================================================
# _generate_chart
# =========================================================================


class TestGenerateChart:
    """Test the _generate_chart private method directly."""

    def _agent(self, mock_connector):
        return _make_agent(mock_connector)

    def test_basic_bar_chart(self, mock_connector):
        agent = self._agent(mock_connector)
        results = [
            {"category": "A", "revenue": 100},
            {"category": "B", "revenue": 200},
            {"category": "C", "revenue": 300},
        ]
        chart = agent._generate_chart(results, {}, "bar")
        assert chart is not None
        assert chart["format"] == "chartjs"
        assert chart["content"]["type"] == "bar"
        assert chart["content"]["data"]["labels"] == ["A", "B", "C"]
        assert chart["content"]["data"]["datasets"][0]["data"] == [100.0, 200.0, 300.0]
        agent.close()

    def test_line_chart(self, mock_connector):
        agent = self._agent(mock_connector)
        results = [
            {"month": "Jan", "sales": 10},
            {"month": "Feb", "sales": 20},
        ]
        chart = agent._generate_chart(results, {}, "line")
        assert chart["content"]["type"] == "line"
        agent.close()

    def test_pie_chart(self, mock_connector):
        agent = self._agent(mock_connector)
        results = [
            {"status": "active", "count": 50},
            {"status": "inactive", "count": 30},
        ]
        chart = agent._generate_chart(results, {}, "pie")
        assert chart["content"]["type"] == "pie"
        # Pie charts should have backgroundColor as an array per slice
        ds = chart["content"]["data"]["datasets"][0]
        assert isinstance(ds["backgroundColor"], list)
        agent.close()

    def test_returns_none_for_insufficient_data(self, mock_connector):
        agent = self._agent(mock_connector)
        # Only 1 row
        results = [{"name": "A", "val": 1}]
        assert agent._generate_chart(results, {}, "bar") is None
        # Empty
        assert agent._generate_chart([], {}, "bar") is None
        agent.close()

    def test_returns_none_no_label_col(self, mock_connector):
        agent = self._agent(mock_connector)
        # All numeric — no string column for labels
        results = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
        assert agent._generate_chart(results, {}, "bar") is None
        agent.close()

    def test_returns_none_no_value_col(self, mock_connector):
        agent = self._agent(mock_connector)
        # All strings — no numeric column for values
        results = [{"a": "x", "b": "y"}, {"a": "z", "b": "w"}]
        assert agent._generate_chart(results, {}, "bar") is None
        agent.close()

    def test_handles_none_values(self, mock_connector):
        agent = self._agent(mock_connector)
        results = [
            {"name": "A", "val": 100},
            {"name": None, "val": None},
            {"name": "C", "val": 300},
        ]
        chart = agent._generate_chart(results, {}, "bar")
        assert chart is not None
        labels = chart["content"]["data"]["labels"]
        values = chart["content"]["data"]["datasets"][0]["data"]
        assert labels == ["A", "", "C"]  # None → ""
        assert values == [100.0, 0.0, 300.0]  # None → 0
        agent.close()

    def test_handles_decimal_values(self, mock_connector):
        agent = self._agent(mock_connector)
        results = [
            {"name": "A", "amount": Decimal("99.99")},
            {"name": "B", "amount": Decimal("200.50")},
        ]
        chart = agent._generate_chart(results, {}, "bar")
        assert chart is not None
        values = chart["content"]["data"]["datasets"][0]["data"]
        assert values == [99.99, 200.50]
        assert all(isinstance(v, float) for v in values)
        agent.close()

    def test_scans_multiple_rows_for_type_detection(self, mock_connector):
        agent = self._agent(mock_connector)
        # First row has None for 'category', but later rows have strings
        results = [
            {"category": None, "count": 10},
            {"category": "A", "count": 20},
            {"category": "B", "count": 30},
        ]
        chart = agent._generate_chart(results, {}, "bar")
        assert chart is not None
        # Should detect category as the label column despite first row being None
        assert chart["content"]["data"]["labels"] == ["", "A", "B"]
        agent.close()

    def test_limits_datasets_to_two(self, mock_connector):
        agent = self._agent(mock_connector)
        results = [
            {"name": "A", "val1": 1, "val2": 2, "val3": 3},
            {"name": "B", "val1": 4, "val2": 5, "val3": 6},
        ]
        chart = agent._generate_chart(results, {}, "bar")
        assert chart is not None
        assert len(chart["content"]["data"]["datasets"]) <= 2
        agent.close()

    def test_title_format(self, mock_connector):
        agent = self._agent(mock_connector)
        results = [
            {"product_name": "Widget", "total_revenue": 1000},
            {"product_name": "Gadget", "total_revenue": 2000},
        ]
        chart = agent._generate_chart(results, {}, "bar")
        assert chart is not None
        assert "Total Revenue" in chart["title"]
        assert "Product Name" in chart["title"]
        agent.close()

    def test_unknown_viz_defaults_to_bar(self, mock_connector):
        agent = self._agent(mock_connector)
        results = [
            {"name": "A", "val": 1},
            {"name": "B", "val": 2},
        ]
        chart = agent._generate_chart(results, {}, "scatter")
        assert chart["content"]["type"] == "bar"
        agent.close()

    def test_chart_options_has_responsive(self, mock_connector):
        agent = self._agent(mock_connector)
        results = [
            {"name": "A", "val": 1},
            {"name": "B", "val": 2},
        ]
        chart = agent._generate_chart(results, {}, "bar")
        assert chart["content"]["options"]["responsive"] is True
        agent.close()
