"""Tests for Annie SDK response types."""

from __future__ import annotations

import json

from annie_sdk.response import (
    AgentResponse,
    ChartContent,
    ChartFormat,
    DataContent,
    ErrorContent,
    ResponseType,
    TextContent,
    _JSONSafeDict,
    _make_json_safe,
)


# =========================================================================
# TextContent
# =========================================================================


class TestTextContent:
    def test_init(self):
        t = TextContent("hello")
        assert t.type == ResponseType.TEXT
        assert t.content == "hello"

    def test_to_dict(self):
        d = TextContent("hi").to_dict()
        assert d == {"type": "text", "content": "hi"}

    def test_str_returns_content(self):
        assert str(TextContent("world")) == "world"

    def test_repr(self):
        r = repr(TextContent("x"))
        assert "TextContent" in r
        assert "x" in r


# =========================================================================
# _JSONSafeDict
# =========================================================================


class TestJSONSafeDict:
    def test_str_uses_json(self):
        d = _JSONSafeDict({"key": "value"})
        output = str(d)
        assert '"key"' in output  # double quotes, not single
        assert '"value"' in output

    def test_repr_uses_json(self):
        d = _JSONSafeDict({"a": 1})
        output = repr(d)
        assert '"a"' in output

    def test_true_becomes_lowercase(self):
        d = _JSONSafeDict({"flag": True})
        output = str(d)
        assert "true" in output
        assert "True" not in output

    def test_false_becomes_lowercase(self):
        d = _JSONSafeDict({"flag": False})
        output = str(d)
        assert "false" in output
        assert "False" not in output

    def test_none_becomes_null(self):
        d = _JSONSafeDict({"val": None})
        output = str(d)
        assert "null" in output
        assert "None" not in output

    def test_still_behaves_as_dict(self):
        d = _JSONSafeDict({"a": 1, "b": 2})
        assert d["a"] == 1
        assert list(d.keys()) == ["a", "b"]
        assert len(d) == 2
        d["c"] = 3
        assert d["c"] == 3

    def test_str_is_valid_json(self):
        d = _JSONSafeDict({"x": True, "y": None, "z": [1, 2]})
        parsed = json.loads(str(d))
        assert parsed == {"x": True, "y": None, "z": [1, 2]}


# =========================================================================
# _make_json_safe
# =========================================================================


class TestMakeJsonSafe:
    def test_wraps_top_level_dict(self):
        result = _make_json_safe({"a": 1})
        assert isinstance(result, _JSONSafeDict)

    def test_wraps_nested_dicts(self):
        result = _make_json_safe({"outer": {"inner": 1}})
        assert isinstance(result["outer"], _JSONSafeDict)

    def test_wraps_dicts_in_lists(self):
        result = _make_json_safe([{"a": 1}, {"b": 2}])
        assert isinstance(result[0], _JSONSafeDict)
        assert isinstance(result[1], _JSONSafeDict)

    def test_leaves_primitives_unchanged(self):
        assert _make_json_safe("hello") == "hello"
        assert _make_json_safe(42) == 42
        assert _make_json_safe(True) is True
        assert _make_json_safe(None) is None

    def test_deeply_nested(self):
        data = {"a": {"b": {"c": {"d": True}}}}
        result = _make_json_safe(data)
        assert isinstance(result["a"]["b"]["c"], _JSONSafeDict)
        output = str(result["a"]["b"]["c"])
        assert "true" in output
        assert "True" not in output


# =========================================================================
# ChartContent
# =========================================================================


class TestChartContent:
    def _sample_content(self):
        return {
            "type": "bar",
            "data": {"labels": ["A", "B"], "datasets": [{"data": [1, 2]}]},
            "options": {"responsive": True, "plugins": {"title": {"display": True, "text": "Test"}}},
        }

    def test_init_defaults(self):
        chart = ChartContent(content={"type": "bar"})
        assert chart.type == ResponseType.CHART
        assert chart.format == ChartFormat.CHARTJS
        assert chart.title is None

    def test_content_wrapped_in_json_safe_dict(self):
        chart = ChartContent(content=self._sample_content())
        assert isinstance(chart.content, _JSONSafeDict)
        # Nested too
        assert isinstance(chart.content["options"], _JSONSafeDict)

    def test_to_dict_with_title(self):
        chart = ChartContent(content={"type": "bar"}, title="My Chart")
        d = chart.to_dict()
        assert d["type"] == "chart"
        assert d["format"] == "chartjs"
        assert d["title"] == "My Chart"
        assert "content" in d

    def test_to_dict_without_title(self):
        chart = ChartContent(content={"type": "bar"}, title=None)
        d = chart.to_dict()
        assert "title" not in d

    def test_to_json_produces_valid_json(self):
        chart = ChartContent(content=self._sample_content(), title="Test")
        json_str = chart.to_json()
        parsed = json.loads(json_str)
        assert parsed["content"]["options"]["responsive"] is True

    def test_str_is_json(self):
        chart = ChartContent(content=self._sample_content())
        output = str(chart)
        parsed = json.loads(output)  # should not raise
        assert parsed["type"] == "bar"

    def test_str_has_no_python_booleans(self):
        chart = ChartContent(content=self._sample_content())
        output = str(chart)
        assert "True" not in output
        assert "False" not in output
        assert "None" not in output
        assert "true" in output

    def test_content_str_has_no_python_booleans(self):
        """print(chart.content) should also produce valid JSON."""
        chart = ChartContent(content=self._sample_content())
        output = str(chart.content)
        assert "True" not in output
        assert "true" in output

    def test_nested_dict_access_still_json_safe(self):
        chart = ChartContent(content=self._sample_content())
        output = str(chart.content["options"])
        assert "true" in output
        assert "True" not in output

    def test_repr(self):
        chart = ChartContent(content={}, title="T")
        r = repr(chart)
        assert "ChartContent" in r
        assert "chartjs" in r
        assert "T" in r


# =========================================================================
# DataContent
# =========================================================================


class TestDataContent:
    def test_init(self):
        data = DataContent(columns=["a", "b"], rows=[[1, 2], [3, 4]])
        assert data.type == ResponseType.DATA
        assert data.columns == ["a", "b"]
        assert len(data.rows) == 2

    def test_row_count(self):
        data = DataContent(columns=["x"], rows=[[1], [2], [3]])
        assert data.row_count == 3

    def test_row_count_empty(self):
        data = DataContent(columns=[], rows=[])
        assert data.row_count == 0

    def test_to_dict(self):
        data = DataContent(columns=["a"], rows=[[1]])
        d = data.to_dict()
        assert d["type"] == "data"
        assert d["columns"] == ["a"]
        assert d["rows"] == [[1]]
        assert d["row_count"] == 1

    def test_to_json_produces_valid_json(self):
        data = DataContent(columns=["a"], rows=[[1]])
        parsed = json.loads(data.to_json())
        assert parsed["columns"] == ["a"]

    def test_to_records(self):
        data = DataContent(columns=["name", "val"], rows=[["A", 1], ["B", 2]])
        records = data.to_records()
        assert records == [{"name": "A", "val": 1}, {"name": "B", "val": 2}]

    def test_to_records_empty(self):
        data = DataContent(columns=["a"], rows=[])
        assert data.to_records() == []

    def test_str_is_json(self):
        data = DataContent(columns=["x"], rows=[[1]])
        parsed = json.loads(str(data))
        assert parsed["type"] == "data"

    def test_repr(self):
        data = DataContent(columns=["a", "b"], rows=[[1, 2]])
        r = repr(data)
        assert "DataContent" in r
        assert "row_count=1" in r


# =========================================================================
# ErrorContent
# =========================================================================


class TestErrorContent:
    def test_init(self):
        err = ErrorContent("oops")
        assert err.type == ResponseType.ERROR
        assert err.content == "oops"
        assert err.code is None

    def test_to_dict_with_code(self):
        err = ErrorContent("fail", code="ERR_001")
        d = err.to_dict()
        assert d["type"] == "error"
        assert d["content"] == "fail"
        assert d["code"] == "ERR_001"

    def test_to_dict_without_code(self):
        d = ErrorContent("fail").to_dict()
        assert "code" not in d

    def test_str_returns_message(self):
        assert str(ErrorContent("broken")) == "broken"

    def test_repr(self):
        r = repr(ErrorContent("x", code="C"))
        assert "ErrorContent" in r
        assert "x" in r
        assert "C" in r


# =========================================================================
# AgentResponse
# =========================================================================


class TestAgentResponse:
    def test_defaults(self):
        r = AgentResponse()
        assert r.success is True
        assert r.visualization == "table"
        assert r.sql is None
        assert r.dsl is None
        assert len(r.items) == 0

    def test_add_text_returns_self(self):
        r = AgentResponse()
        result = r.add_text("hello")
        assert result is r
        assert len(r.items) == 1

    def test_add_chart(self):
        r = AgentResponse()
        r.add_chart(content={"type": "bar"}, title="My Chart")
        assert len(r.items) == 1
        assert isinstance(r.items[0], ChartContent)

    def test_add_data(self):
        r = AgentResponse()
        r.add_data(columns=["a"], rows=[[1]])
        assert len(r.items) == 1
        assert isinstance(r.items[0], DataContent)

    def test_add_error_sets_success_false(self):
        r = AgentResponse()
        assert r.success is True
        r.add_error("fail")
        assert r.success is False

    def test_text_accessor(self):
        r = AgentResponse()
        assert r.text is None
        r.add_text("hello")
        assert r.text == "hello"

    def test_texts_accessor(self):
        r = AgentResponse()
        r.add_text("a").add_text("b")
        assert r.texts == ["a", "b"]

    def test_chart_accessor(self):
        r = AgentResponse()
        assert r.chart is None
        r.add_chart(content={"type": "bar"})
        assert r.chart is not None
        assert r.chart.format == ChartFormat.CHARTJS

    def test_charts_accessor(self):
        r = AgentResponse()
        r.add_chart(content={"t": "a"}).add_chart(content={"t": "b"})
        assert len(r.charts) == 2

    def test_data_accessor(self):
        r = AgentResponse()
        assert r.data is None
        r.add_data(columns=["a"], rows=[[1]])
        assert r.data is not None
        assert r.data.row_count == 1

    def test_error_accessor(self):
        r = AgentResponse()
        assert r.error is None
        r.add_error("err")
        assert r.error is not None
        assert r.error.content == "err"

    def test_from_error_factory(self):
        r = AgentResponse.from_error("kaboom", code="E1")
        assert r.success is False
        assert r.error is not None
        assert r.error.content == "kaboom"
        assert r.error.code == "E1"

    def test_to_dict(self):
        r = AgentResponse(sql="SELECT 1", visualization="bar", success=True)
        r.add_text("insight")
        d = r.to_dict()
        assert d["sql"] == "SELECT 1"
        assert d["visualization"] == "bar"
        assert d["success"] is True
        assert len(d["items"]) == 1

    def test_to_json_valid(self):
        r = AgentResponse()
        r.add_chart(content={"responsive": True})
        json_str = r.to_json()
        parsed = json.loads(json_str)
        assert parsed["success"] is True

    def test_to_json_no_python_booleans(self):
        r = AgentResponse()
        r.add_chart(content={"responsive": True})
        json_str = r.to_json()
        assert "True" not in json_str
        assert "true" in json_str

    def test_iteration(self):
        r = AgentResponse()
        r.add_text("a").add_data(columns=["x"], rows=[[1]])
        items = list(r)
        assert len(items) == 2

    def test_len(self):
        r = AgentResponse()
        assert len(r) == 0
        r.add_text("x")
        assert len(r) == 1

    def test_sql_and_dsl_metadata(self):
        r = AgentResponse(sql="SELECT 1", dsl={"source": "orders"})
        assert r.sql == "SELECT 1"
        assert r.dsl == {"source": "orders"}

    def test_str_is_valid_json(self):
        r = AgentResponse()
        r.add_chart(content={"flag": True})
        output = str(r)
        parsed = json.loads(output)
        assert parsed["success"] is True

    def test_repr(self):
        r = AgentResponse()
        r.add_text("x")
        rep = repr(r)
        assert "AgentResponse" in rep
        assert "text" in rep
