"""Integration tests for validate_handler.validate_deltas.

Exercises the full chain: snapshot_table → compute_table_delta →
compare_table_deltas using fake connection objects.  No patches on
inner functions — only the outermost DB boundary (the connection
object) is replaced with a fake.
"""

from __future__ import annotations

from unittest.mock import MagicMock, PropertyMock

from test_runner.common.config import ReplayConfiguration
from test_runner.common.models.baseline_data import BaselineData
from test_runner.replay.isolation import SnowflakeCloneIsolation
from test_runner.replay.validate_handler import (
    DeltaComparisonResult,
    validate_deltas,
)


# ---------------------------------------------------------------------------
# Fake DB boundary objects
# ---------------------------------------------------------------------------

class _FakeCursor:
    """Minimal DB-API cursor returning canned description + rows."""

    def __init__(self, description=None, rows=None):
        self.description = description
        self._rows = rows or []
        self.executed: list[str] = []
        self._closed = False

    def execute(self, sql: str) -> None:
        self.executed.append(sql)

    def fetchall(self):
        return self._rows

    def close(self) -> None:
        self._closed = True


class _FakeConnection:
    """Returns pre-built cursors in FIFO order."""

    def __init__(self, cursors: list[_FakeCursor]):
        self._cursors = list(cursors)
        self._idx = 0

    def cursor(self) -> _FakeCursor:
        c = self._cursors[self._idx]
        self._idx += 1
        return c


def _snapshot_cursor(description, rows):
    """Cursor that satisfies snapshot_table's protocol."""
    return _FakeCursor(description=description, rows=rows)


def _baseline(**overrides) -> BaselineData:
    """Build a minimal BaselineData with sensible defaults."""
    defaults = dict(
        procedure="dbo.MyProc",
        parameters={},
        captured_at="2025-01-01T00:00:00Z",
        result_sets=[],
        row_counts=[],
        success=True,
    )
    defaults.update(overrides)
    return BaselineData(**defaults)


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------

class TestValidateDeltasIntegration:

    def test_matching_deltas_return_match(self):
        """Baseline insert matches actual insert → delta_match."""
        conn = _FakeConnection([
            _snapshot_cursor([("color",)], []),
            _snapshot_cursor([("color",)], [("Blue",)]),
        ])
        baseline = _baseline(table_deltas={
            "WAREHOUSE.COLORS": {
                "table": "WAREHOUSE.COLORS",
                "strategy_used": "full_snapshot",
                "inserts": [{"COLOR": "Blue"}],
                "deletes": [],
                "updates": [],
            },
        })

        result = validate_deltas(
            connection=conn,
            baseline=baseline,
            replay_cfg=ReplayConfiguration(),
            execute_fn=lambda db: None,
            clone_db="CLONE",
        )

        assert result.match is True
        assert result.match_type == "delta_match"
        assert result.details["WAREHOUSE.COLORS"]["match"] is True

    def test_mismatched_insert_values_return_content_mismatch(self):
        """Baseline expects Blue but actual produces Red → content mismatch."""
        conn = _FakeConnection([
            _snapshot_cursor([("color",)], []),
            _snapshot_cursor([("color",)], [("Red",)]),
        ])
        baseline = _baseline(table_deltas={
            "WAREHOUSE.COLORS": {
                "table": "WAREHOUSE.COLORS",
                "strategy_used": "full_snapshot",
                "inserts": [{"COLOR": "Blue"}],
                "deletes": [],
                "updates": [],
            },
        })

        result = validate_deltas(
            connection=conn,
            baseline=baseline,
            replay_cfg=ReplayConfiguration(),
            execute_fn=lambda db: None,
            clone_db="CLONE",
        )

        assert result.match is False
        assert result.match_type == "delta_mismatch"
        detail = result.details["WAREHOUSE.COLORS"]
        assert detail["match"] is False
        assert any(
            d["type"] == "insert_content_mismatch"
            for d in detail["differences"]
        )

    def test_extra_inserts_return_count_mismatch(self):
        """Baseline has 1 insert, actual produces 2 → insert_count_mismatch."""
        conn = _FakeConnection([
            _snapshot_cursor([("id",)], []),
            _snapshot_cursor([("id",)], [(1,), (2,)]),
        ])
        baseline = _baseline(table_deltas={
            "WAREHOUSE.T": {
                "table": "WAREHOUSE.T",
                "strategy_used": "full_snapshot",
                "inserts": [{"ID": 1}],
                "deletes": [],
                "updates": [],
            },
        })

        result = validate_deltas(
            connection=conn,
            baseline=baseline,
            replay_cfg=ReplayConfiguration(),
            execute_fn=lambda db: None,
            clone_db="CLONE",
        )

        assert result.match is False
        diffs = result.details["WAREHOUSE.T"]["differences"]
        assert any(d["type"] == "insert_count_mismatch" for d in diffs)
        mismatch = next(d for d in diffs if d["type"] == "insert_count_mismatch")
        assert mismatch["baseline"] == 1
        assert mismatch["actual"] == 2

    def test_ignore_columns_from_column_metadata_flow_through(self):
        """Platform-managed columns in column_metadata are excluded end-to-end.

        The chain: column_metadata → _merge_column_metadata → tc.ignore_columns
        → FullSnapshotStrategy._resolve_columns → compare_table_deltas._strip_columns.
        """
        conn = _FakeConnection([
            _snapshot_cursor([("id",), ("validfrom",)], []),
            _snapshot_cursor([("id",), ("validfrom",)], [(1, "2025-01-01")]),
        ])
        baseline = _baseline(
            table_deltas={
                "DBO.ORDERS": {
                    "table": "DBO.ORDERS",
                    "strategy_used": "full_snapshot",
                    "inserts": [{"ID": 1, "VALIDFROM": "2024-01-01"}],
                    "deletes": [],
                    "updates": [],
                },
            },
            column_metadata={
                "DBO.ORDERS": {
                    "platform_managed_columns": ["VALIDFROM"],
                },
            },
        )

        result = validate_deltas(
            connection=conn,
            baseline=baseline,
            replay_cfg=ReplayConfiguration(),
            execute_fn=lambda db: None,
            clone_db="CLONE",
        )

        assert result.match is True
        assert result.match_type == "delta_match"

    def test_normalize_fqn_strips_database_prefix(self):
        """3-part baseline key 'DB.Schema.Table' normalizes to 'SCHEMA.TABLE'."""
        conn = _FakeConnection([
            _snapshot_cursor([("id",)], []),
            _snapshot_cursor([("id",)], [(1,)]),
        ])
        baseline = _baseline(table_deltas={
            "WideWorldImporters.Warehouse.Colors": {
                "table": "WideWorldImporters.Warehouse.Colors",
                "strategy_used": "full_snapshot",
                "inserts": [{"ID": 1}],
                "deletes": [],
                "updates": [],
            },
        })

        result = validate_deltas(
            connection=conn,
            baseline=baseline,
            replay_cfg=ReplayConfiguration(),
            execute_fn=lambda db: None,
            clone_db="CLONE",
        )

        assert result.match is True
        assert "WAREHOUSE.COLORS" in result.details

    def test_no_baseline_deltas_with_affected_tables_detects_mutations(self):
        """Empty baseline + affected_tables + actual has changes → mismatch.

        Even when the source procedure produced no deltas, if the CUR says
        the procedure modifies_data and Snowflake produces unexpected inserts,
        we detect it.
        """
        conn = _FakeConnection([
            _snapshot_cursor([("id",)], []),
            _snapshot_cursor([("id",)], [(1,)]),
        ])
        baseline = _baseline(table_deltas=None)

        result = validate_deltas(
            connection=conn,
            baseline=baseline,
            replay_cfg=ReplayConfiguration(),
            execute_fn=lambda db: None,
            affected_tables=["Warehouse.Colors"],
            clone_db="CLONE",
        )

        assert result.match is False
        assert result.match_type == "delta_mismatch"
        diffs = result.details["WAREHOUSE.COLORS"]["differences"]
        assert any(d["type"] == "insert_count_mismatch" for d in diffs)

    def test_no_tables_returns_no_deltas(self):
        """No baseline deltas + no affected_tables → early-exit no_deltas."""
        conn = _FakeConnection([])
        baseline = _baseline(table_deltas=None)

        result = validate_deltas(
            connection=conn,
            baseline=baseline,
            replay_cfg=ReplayConfiguration(),
            execute_fn=lambda db: None,
            clone_db="CLONE",
        )

        assert result.match is True
        assert result.match_type == "no_deltas"
        assert result.details == {}

    def test_owns_isolation_setup_and_teardown_called(self):
        """clone_db=None → executor_factory.create_isolation() invoked;
        isolation.setup() and teardown() bracket the snapshot+execute cycle.

        Isolation is an infrastructure boundary, so mocking it is justified —
        we are NOT mocking any of the snapshot/delta/compare inner functions.
        """
        mock_isolation = MagicMock(spec=SnowflakeCloneIsolation)
        type(mock_isolation).clone_database = PropertyMock(
            return_value="AUTO_CLONE",
        )
        mock_factory = MagicMock()
        mock_factory.create_isolation.return_value = mock_isolation

        conn = _FakeConnection([
            _snapshot_cursor([("id",)], []),
            _snapshot_cursor([("id",)], []),
        ])
        baseline = _baseline(table_deltas={
            "DBO.T": {
                "table": "DBO.T",
                "strategy_used": "full_snapshot",
                "inserts": [],
                "deletes": [],
                "updates": [],
            },
        })
        execute_calls: list[str | None] = []

        result = validate_deltas(
            connection=conn,
            baseline=baseline,
            replay_cfg=ReplayConfiguration(),
            execute_fn=lambda db: execute_calls.append(db),
            database="MYDB",
            procedure="dbo.MyProc",
            params_hash="abc123",
            executor_factory=mock_factory,
        )

        mock_factory.create_isolation.assert_called_once_with(
            source_database="MYDB",
            procedure="dbo.MyProc",
            params_hash="abc123",
        )
        mock_isolation.setup.assert_called_once_with(conn)
        mock_isolation.teardown.assert_called_once_with(conn)
        assert execute_calls == ["AUTO_CLONE"]
        assert result.match is True

    def test_caller_provided_clone_db_skips_isolation(self):
        """clone_db='PRE_CLONE' → no executor_factory needed, no isolation."""
        conn = _FakeConnection([
            _snapshot_cursor([("id",)], []),
            _snapshot_cursor([("id",)], []),
        ])
        baseline = _baseline(table_deltas={
            "DBO.T": {
                "table": "DBO.T",
                "strategy_used": "full_snapshot",
                "inserts": [],
                "deletes": [],
                "updates": [],
            },
        })
        execute_calls: list[str | None] = []

        result = validate_deltas(
            connection=conn,
            baseline=baseline,
            replay_cfg=ReplayConfiguration(),
            execute_fn=lambda db: execute_calls.append(db),
            clone_db="PRE_CLONE",
        )

        assert execute_calls == ["PRE_CLONE"]
        assert result.match is True
