"""Tests for replay.source_introspector."""

from __future__ import annotations

from unittest.mock import MagicMock

from test_runner.replay.models import TableConfig
from test_runner.replay.source_introspector import (
    DefaultIntrospector,
    RedshiftIntrospector,
    SqlServerIntrospector,
    TeradataIntrospector,
)


# ---------------------------------------------------------------------------
# normalize_case
# ---------------------------------------------------------------------------


class TestNormalizeCase:
    def test_sqlserver_preserves_case(self):
        introspector = SqlServerIntrospector()
        assert introspector.normalize_case("Warehouse") == "Warehouse"

    def test_redshift_lowercases(self):
        introspector = RedshiftIntrospector()
        assert introspector.normalize_case("Public") == "public"
        assert introspector.normalize_case("USERS") == "users"

    def test_teradata_uppercases(self):
        introspector = TeradataIntrospector()
        assert introspector.normalize_case("mydb") == "MYDB"
        assert introspector.normalize_case("Orders") == "ORDERS"

    def test_default_uppercases(self):
        introspector = DefaultIntrospector()
        assert introspector.normalize_case("public") == "PUBLIC"
        assert introspector.normalize_case("Users") == "USERS"


# ---------------------------------------------------------------------------
# SqlServerIntrospector.introspect_columns
# ---------------------------------------------------------------------------


class TestSqlServerIntrospectColumns:
    def _make_connection(self, rows: list[tuple]) -> MagicMock:
        conn = MagicMock()
        cursor = MagicMock()
        cursor.fetchall.return_value = rows
        conn.cursor.return_value = cursor
        return conn

    def test_temporal_columns_detected(self):
        rows = [
            ("Warehouse", "Colors", "ValidFrom", 1, False, False),
            ("Warehouse", "Colors", "ValidTo", 2, False, False),
        ]
        conn = self._make_connection(rows)
        introspector = SqlServerIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="Warehouse", table_name="Colors")],
        )

        assert "WAREHOUSE.COLORS" in result
        managed = result["WAREHOUSE.COLORS"]["platform_managed_columns"]
        assert "VALIDFROM" in managed
        assert "VALIDTO" in managed

    def test_identity_columns_detected(self):
        rows = [
            ("Warehouse", "Colors", "ColorID", 0, True, False),
        ]
        conn = self._make_connection(rows)
        introspector = SqlServerIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="Warehouse", table_name="Colors")],
        )

        assert "WAREHOUSE.COLORS" in result
        assert "COLORID" in result["WAREHOUSE.COLORS"]["identity_columns"]
        assert "COLORID" not in result["WAREHOUSE.COLORS"]["platform_managed_columns"]

    def test_computed_columns_detected(self):
        rows = [
            ("dbo", "Orders", "TotalWithTax", 0, False, True),
        ]
        conn = self._make_connection(rows)
        introspector = SqlServerIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="dbo", table_name="Orders")],
        )

        assert "DBO.ORDERS" in result
        assert "TOTALWITHTAX" in result["DBO.ORDERS"]["platform_managed_columns"]

    def test_empty_configs_returns_empty(self):
        conn = MagicMock()
        introspector = SqlServerIntrospector()
        result = introspector.introspect_columns(conn, [])
        assert result == {}

    def test_query_failure_returns_empty(self):
        conn = MagicMock()
        cursor = MagicMock()
        cursor.execute.side_effect = Exception("connection lost")
        conn.cursor.return_value = cursor
        introspector = SqlServerIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="dbo", table_name="Foo")],
        )
        assert result == {}

    def test_mixed_column_types(self):
        rows = [
            ("Warehouse", "Colors", "ColorID", 0, True, False),
            ("Warehouse", "Colors", "ValidFrom", 1, False, False),
            ("Warehouse", "Colors", "ValidTo", 2, False, False),
            ("Warehouse", "Colors", "FullName", 0, False, True),
        ]
        conn = self._make_connection(rows)
        introspector = SqlServerIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="Warehouse", table_name="Colors")],
        )

        entry = result["WAREHOUSE.COLORS"]
        assert sorted(entry["platform_managed_columns"]) == [
            "FULLNAME", "VALIDFROM", "VALIDTO",
        ]
        assert entry["identity_columns"] == ["COLORID"]


# ---------------------------------------------------------------------------
# RedshiftIntrospector.introspect_columns
# ---------------------------------------------------------------------------


class TestRedshiftIntrospectColumns:
    def _make_connection(self, rows: list[tuple]) -> MagicMock:
        conn = MagicMock()
        cursor = MagicMock()
        cursor.fetchall.return_value = rows
        conn.cursor.return_value = cursor
        return conn

    def test_identity_columns_detected(self):
        rows = [
            ("public", "users", "user_id", "\"identity\"(100, 0, '1,1'::text)"),
        ]
        conn = self._make_connection(rows)
        introspector = RedshiftIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="public", table_name="users")],
        )

        assert "PUBLIC.USERS" in result
        assert "USER_ID" in result["PUBLIC.USERS"]["identity_columns"]
        assert "USER_ID" in result["PUBLIC.USERS"]["platform_managed_columns"]

    def test_multiple_tables(self):
        rows = [
            ("public", "users", "user_id", "\"identity\"(1, 0, '1,1'::text)"),
            ("staging", "events", "event_id", "\"identity\"(1, 0, '1,1'::text)"),
        ]
        conn = self._make_connection(rows)
        introspector = RedshiftIntrospector()

        result = introspector.introspect_columns(
            conn, [
                TableConfig(schema_name="public", table_name="users"),
                TableConfig(schema_name="staging", table_name="events"),
            ],
        )

        assert "PUBLIC.USERS" in result
        assert "STAGING.EVENTS" in result

    def test_empty_configs_returns_empty(self):
        conn = MagicMock()
        introspector = RedshiftIntrospector()
        result = introspector.introspect_columns(conn, [])
        assert result == {}

    def test_no_identity_columns_returns_empty(self):
        conn = self._make_connection([])
        introspector = RedshiftIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="public", table_name="users")],
        )
        assert result == {}

    def test_query_failure_returns_empty(self):
        conn = MagicMock()
        cursor = MagicMock()
        cursor.execute.side_effect = Exception("connection lost")
        conn.cursor.return_value = cursor
        introspector = RedshiftIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="public", table_name="users")],
        )
        assert result == {}


# ---------------------------------------------------------------------------
# TeradataIntrospector.introspect_columns
# ---------------------------------------------------------------------------


class TestTeradataIntrospectColumns:
    def _make_connection(self, rows: list[tuple]) -> MagicMock:
        conn = MagicMock()
        cursor = MagicMock()
        cursor.fetchall.return_value = rows
        conn.cursor.return_value = cursor
        return conn

    def test_identity_generated_always_detected(self):
        rows = [
            ("mydb     ", "orders   ", "order_id ", "GA"),
        ]
        conn = self._make_connection(rows)
        introspector = TeradataIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="mydb", table_name="orders")],
        )

        assert "MYDB.ORDERS" in result
        assert "ORDER_ID" in result["MYDB.ORDERS"]["identity_columns"]
        assert "ORDER_ID" in result["MYDB.ORDERS"]["platform_managed_columns"]

    def test_identity_generated_by_default_detected(self):
        rows = [
            ("mydb", "events", "event_id", "GD"),
        ]
        conn = self._make_connection(rows)
        introspector = TeradataIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="mydb", table_name="events")],
        )

        assert "MYDB.EVENTS" in result
        assert "EVENT_ID" in result["MYDB.EVENTS"]["identity_columns"]
        assert "EVENT_ID" in result["MYDB.EVENTS"]["platform_managed_columns"]

    def test_multiple_tables(self):
        rows = [
            ("mydb", "orders", "order_id", "GA"),
            ("staging", "events", "event_id", "GD"),
        ]
        conn = self._make_connection(rows)
        introspector = TeradataIntrospector()

        result = introspector.introspect_columns(
            conn, [
                TableConfig(schema_name="mydb", table_name="orders"),
                TableConfig(schema_name="staging", table_name="events"),
            ],
        )

        assert "MYDB.ORDERS" in result
        assert "STAGING.EVENTS" in result

    def test_empty_configs_returns_empty(self):
        conn = MagicMock()
        introspector = TeradataIntrospector()
        result = introspector.introspect_columns(conn, [])
        assert result == {}

    def test_no_identity_columns_returns_empty(self):
        conn = self._make_connection([])
        introspector = TeradataIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="mydb", table_name="orders")],
        )
        assert result == {}

    def test_query_failure_returns_empty(self):
        conn = MagicMock()
        cursor = MagicMock()
        cursor.execute.side_effect = Exception("connection lost")
        conn.cursor.return_value = cursor
        introspector = TeradataIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="mydb", table_name="orders")],
        )
        assert result == {}

    def test_strips_padded_values(self):
        rows = [
            ("  mydb   ", "  orders  ", "  order_id  ", "GA"),
        ]
        conn = self._make_connection(rows)
        introspector = TeradataIntrospector()

        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="mydb", table_name="orders")],
        )

        assert "MYDB.ORDERS" in result
        assert "ORDER_ID" in result["MYDB.ORDERS"]["identity_columns"]


# ---------------------------------------------------------------------------
# DefaultIntrospector.introspect_columns
# ---------------------------------------------------------------------------


class TestDefaultIntrospectColumns:
    def test_always_returns_empty(self):
        conn = MagicMock()
        introspector = DefaultIntrospector()
        result = introspector.introspect_columns(
            conn, [TableConfig(schema_name="dbo", table_name="Foo")],
        )
        assert result == {}
