"""Tests for data connectors."""

from __future__ import annotations

import json
import tempfile

from orxaq.fabric.connectors import CSVConnector, DictConnector, JSONConnector
from orxaq.fabric.schema import ColumnDef, SchemaSpec


class TestCSVConnector:
    def test_read_csv(self):
        content = "name,value\nAlice,10\nBob,20\n"
        with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
            f.write(content)
            f.flush()
            ds = CSVConnector().read(f.name)

        assert ds.row_count == 2
        assert ds.connector_type == "csv"
        assert ds.data[0]["name"] == "Alice"
        assert ds.data[1]["value"] == "20"

    def test_read_csv_with_schema(self):
        schema = SchemaSpec(
            name="test",
            columns=(
                ColumnDef("name", "str"),
                ColumnDef("value", "float"),
            ),
        )
        content = "name,value\nAlice,10.5\nBob,20.3\n"
        with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
            f.write(content)
            f.flush()
            ds = CSVConnector().read(f.name, schema=schema)

        assert ds.data[0]["value"] == 10.5
        assert isinstance(ds.data[0]["value"], float)

    def test_source_is_filepath(self):
        content = "x\n1\n"
        with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
            f.write(content)
            f.flush()
            ds = CSVConnector().read(f.name)
        assert ds.source == f.name

    def test_columns_property(self):
        content = "a,b,c\n1,2,3\n"
        with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
            f.write(content)
            f.flush()
            ds = CSVConnector().read(f.name)
        assert ds.columns == ["a", "b", "c"]


class TestJSONConnector:
    def test_read_json(self):
        data = [{"x": 1, "y": 2}, {"x": 3, "y": 4}]
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(data, f)
            f.flush()
            ds = JSONConnector().read(f.name)

        assert ds.row_count == 2
        assert ds.connector_type == "json"
        assert ds.data[0]["x"] == 1

    def test_read_json_with_schema(self):
        schema = SchemaSpec(
            name="test",
            columns=(ColumnDef("val", "float"),),
        )
        data = [{"val": "3.14"}]
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(data, f)
            f.flush()
            ds = JSONConnector().read(f.name, schema=schema)

        assert ds.data[0]["val"] == 3.14

    def test_invalid_json_raises(self):
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump({"not": "a list"}, f)
            f.flush()
            try:
                JSONConnector().read(f.name)
                assert False, "Should have raised ValueError"
            except ValueError as e:
                assert "list" in str(e)


class TestDictConnector:
    def test_wrap_data(self):
        data = [{"a": 1}, {"a": 2}]
        ds = DictConnector().read(data)
        assert ds.row_count == 2
        assert ds.connector_type == "dict"
        assert ds.source == "memory"

    def test_to_list_returns_mutable_copy(self):
        data = [{"a": 1}]
        ds = DictConnector().read(data)
        result = ds.to_list()
        result[0]["a"] = 99
        assert ds.data[0]["a"] == 1  # Original unchanged

    def test_empty_data(self):
        ds = DictConnector().read([])
        assert ds.row_count == 0
        assert ds.columns == []

    def test_ingested_at_populated(self):
        ds = DictConnector().read([{"x": 1}])
        assert ds.ingested_at != ""
        assert "T" in ds.ingested_at


class TestDatasetColumns:
    """Cover Dataset.columns from schema (line 34)."""

    def test_columns_from_schema(self):
        from orxaq.fabric.connectors import Dataset

        schema = SchemaSpec(
            name="test",
            columns=(ColumnDef("a", "str"), ColumnDef("b", "int")),
        )
        ds = Dataset(
            source="mem",
            data=({"a": "x", "b": "1"},),
            row_count=1,
            schema=schema,
        )
        assert ds.columns == ["a", "b"]


class TestCastRowEdgeCases:
    """Cover _cast_row branches: int, bool, ValueError, nullable empty."""

    def test_int_cast(self):
        schema = SchemaSpec(
            name="test",
            columns=(ColumnDef("count", "int"),),
        )
        content = "count\n42\n"
        with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
            f.write(content)
            f.flush()
            ds = CSVConnector().read(f.name, schema=schema)
        assert ds.data[0]["count"] == 42
        assert isinstance(ds.data[0]["count"], int)

    def test_bool_cast(self):
        schema = SchemaSpec(
            name="test",
            columns=(ColumnDef("flag", "bool"),),
        )
        content = "flag\ntrue\nfalse\n1\nno\n"
        with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
            f.write(content)
            f.flush()
            ds = CSVConnector().read(f.name, schema=schema)
        assert ds.data[0]["flag"] is True
        assert ds.data[1]["flag"] is False
        assert ds.data[2]["flag"] is True
        assert ds.data[3]["flag"] is False

    def test_cast_error_preserves_value(self):
        from orxaq.fabric.connectors import _cast_row

        schema = SchemaSpec(
            name="test",
            columns=(ColumnDef("num", "int"),),
        )
        result = _cast_row({"num": "not_a_number"}, schema)
        assert result["num"] == "not_a_number"

    def test_nullable_empty_string(self):
        from orxaq.fabric.connectors import _cast_row

        schema = SchemaSpec(
            name="test",
            columns=(ColumnDef("val", "str", nullable=True),),
        )
        result = _cast_row({"val": ""}, schema)
        assert result["val"] is None
