"""Extended tests for test_runner.validate.baseline_loader.

Covers DiskBaselineLoader, create_baseline_loader factory, and
_PreloadedLoader not tested in test_baseline_loader.py.
"""

from __future__ import annotations

import json
from pathlib import Path
from unittest.mock import MagicMock

import pytest

from test_runner.common.models import BaselineData
from test_runner.validate.baseline_loader import (
    BaselineLoader,
    DiskBaselineLoader,
    StageBaselineLoader,
    _PreloadedLoader,
    create_baseline_loader,
)


# ---------------------------------------------------------------------------
# DiskBaselineLoader
# ---------------------------------------------------------------------------

class TestDiskBaselineLoader:
    def _write_baseline(self, base_dir: Path, filename: str, data: dict) -> Path:
        """Write a baseline JSON file and return its path."""
        base_dir.mkdir(parents=True, exist_ok=True)
        path = base_dir / filename
        path.write_text(json.dumps(data), encoding="utf-8")
        return path

    def test_loads_baselines_from_disk(self, tmp_path: Path):
        bl_dir = tmp_path / "baselines" / "20260101" / "dbo_GetProducts"
        self._write_baseline(bl_dir, "case_000_abc.json", {
            "procedure": "dbo.GetProducts",
            "parameters": {},
            "captured_at": "2026-01-01T00:00:00Z",
            "result_sets": [[{"Id": 1}]],
            "row_counts": [1],
            "success": True,
        })

        loader = DiskBaselineLoader(tmp_path / "baselines")
        baselines = loader.load()

        assert len(baselines) == 1
        assert baselines[0].procedure == "dbo.GetProducts"
        assert baselines[0].success is True

    def test_multiple_baselines(self, tmp_path: Path):
        bl_dir = tmp_path / "baselines" / "20260101" / "dbo_GetProducts"
        for i in range(3):
            self._write_baseline(bl_dir, f"case_{i:03d}_h{i}.json", {
                "procedure": "dbo.GetProducts",
                "parameters": {"id": i},
                "captured_at": "2026-01-01T00:00:00Z",
                "result_sets": [],
                "row_counts": [],
                "success": True,
            })

        loader = DiskBaselineLoader(tmp_path / "baselines")
        baselines = loader.load()
        assert len(baselines) == 3

    def test_empty_directory(self, tmp_path: Path):
        empty = tmp_path / "baselines"
        empty.mkdir()
        loader = DiskBaselineLoader(empty)
        assert loader.load() == []

    def test_nonexistent_directory(self, tmp_path: Path):
        loader = DiskBaselineLoader(tmp_path / "nonexistent")
        assert loader.load() == []

    def test_source_label_includes_path(self, tmp_path: Path):
        loader = DiskBaselineLoader(tmp_path / "baselines")
        assert "baselines" in loader.source_label
        assert "(local)" in loader.source_label

    def test_invalid_json_skipped(self, tmp_path: Path):
        bl_dir = tmp_path / "baselines" / "20260101" / "proc"
        bl_dir.mkdir(parents=True)
        (bl_dir / "bad.json").write_text("not valid json")
        self._write_baseline(bl_dir, "good.json", {
            "procedure": "P",
            "parameters": {},
            "captured_at": "2026-01-01T00:00:00Z",
            "result_sets": [],
            "row_counts": [],
            "success": True,
        })

        loader = DiskBaselineLoader(tmp_path / "baselines")
        baselines = loader.load()
        # bad.json should be skipped, only the good one loaded
        assert len(baselines) == 1

    def test_baselines_with_error(self, tmp_path: Path):
        bl_dir = tmp_path / "baselines" / "20260101" / "dbo_P"
        self._write_baseline(bl_dir, "case_000_h1.json", {
            "procedure": "dbo.P",
            "parameters": {},
            "captured_at": "2026-01-01T00:00:00Z",
            "result_sets": [],
            "row_counts": [],
            "success": False,
            "error": "timeout",
        })

        loader = DiskBaselineLoader(tmp_path / "baselines")
        baselines = loader.load()
        assert len(baselines) == 1
        assert baselines[0].success is False
        assert baselines[0].error == "timeout"

    def test_baselines_with_column_types(self, tmp_path: Path):
        bl_dir = tmp_path / "baselines" / "20260101" / "dbo_P"
        self._write_baseline(bl_dir, "case_000_h1.json", {
            "procedure": "dbo.P",
            "parameters": {},
            "captured_at": "2026-01-01T00:00:00Z",
            "result_sets": [[{"ID": 1}]],
            "row_counts": [1],
            "success": True,
            "column_types": [{"ID": "NUMBER"}],
        })

        loader = DiskBaselineLoader(tmp_path / "baselines")
        baselines = loader.load()
        assert baselines[0].column_types == [{"ID": "NUMBER"}]


# ---------------------------------------------------------------------------
# _PreloadedLoader
# ---------------------------------------------------------------------------

class TestPreloadedLoader:
    def test_returns_preloaded_baselines(self):
        baselines = [
            BaselineData(
                procedure="P1", parameters={}, captured_at="2026-01-01",
                result_sets=[], row_counts=[], success=True,
            ),
            BaselineData(
                procedure="P2", parameters={}, captured_at="2026-01-01",
                result_sets=[], row_counts=[], success=True,
            ),
        ]
        loader = _PreloadedLoader(baselines, "test label")
        assert loader.load() == baselines
        assert loader.source_label == "test label"

    def test_empty_preloaded(self):
        loader = _PreloadedLoader([], "empty")
        assert loader.load() == []


# ---------------------------------------------------------------------------
# StageBaselineLoader (additional tests)
# ---------------------------------------------------------------------------

class TestStageBaselineLoaderExtended:
    def test_error_baseline_loaded_correctly(self):
        connector = MagicMock()
        connector.execute_query.return_value = [{
            "PROCEDURE_NAME": "dbo.P",
            "PARAMS_HASH": "h1",
            "PARAMS": "{}",
            "CAPTURED_AT": "2026-01-01T00:00:00Z",
            "SUCCESS": False,
            "ERROR": "timeout error",
            "ROW_COUNTS": None,
        }]
        loader = StageBaselineLoader(connector, "MYDB")
        baselines = loader.load()

        assert len(baselines) == 1
        assert baselines[0].success is False
        assert baselines[0].error == "timeout error"

    def test_stored_params_hash_used(self):
        connector = MagicMock()
        connector.execute_query.return_value = [{
            "PROCEDURE_NAME": "dbo.P",
            "PARAMS_HASH": "custom_hash",
            "PARAMS": '{"x": 1}',
            "CAPTURED_AT": "2026-01-01",
            "SUCCESS": True,
            "ERROR": None,
        }]
        loader = StageBaselineLoader(connector, "DB")
        baselines = loader.load()
        assert baselines[0].params_hash == "custom_hash"

    def test_source_label(self):
        connector = MagicMock()
        loader = StageBaselineLoader(connector, "MYDB")
        assert "MYDB" in loader.source_label
        assert "(stage)" in loader.source_label

    def test_ensure_infrastructure_called(self):
        connector = MagicMock()
        connector.execute_query.return_value = []
        loader = StageBaselineLoader(connector, "DB")
        loader.load()
        infra_calls = [
            c.args[0] for c in connector.execute_statement.call_args_list
        ]
        assert any("CREATE DATABASE" in sql for sql in infra_calls)
        assert any("CREATE SCHEMA" in sql for sql in infra_calls)
        assert any("CREATE STAGE" in sql for sql in infra_calls)

    def test_stage_query_failure_returns_empty(self):
        connector = MagicMock()
        connector.execute_query.side_effect = RuntimeError("stage not found")
        loader = StageBaselineLoader(connector, "DB")
        assert loader.load() == []

    def test_malformed_row_skipped(self):
        connector = MagicMock()
        connector.execute_query.return_value = [
            {"PROCEDURE_NAME": None, "PARAMS": "invalid json{{{"},
            {
                "PROCEDURE_NAME": "dbo.P",
                "PARAMS": '{"a": 1}',
                "CAPTURED_AT": "2026-01-01",
                "SUCCESS": True,
                "ERROR": None,
            },
        ]
        loader = StageBaselineLoader(connector, "DB")
        baselines = loader.load()
        # At least the second valid row should be loaded
        assert any(bl.procedure == "dbo.P" for bl in baselines)


# ---------------------------------------------------------------------------
# create_baseline_loader factory
# ---------------------------------------------------------------------------

class TestCreateBaselineLoader:
    def test_returns_disk_loader_when_no_connector(self, tmp_path: Path):
        loader = create_baseline_loader(
            connector=None,
            database="DB",
            baseline_dir=tmp_path / "baselines",
            baseline_stage=None,
            project_root=tmp_path,
        )
        assert isinstance(loader, DiskBaselineLoader)

    def test_returns_disk_loader_when_stage_empty(self, tmp_path: Path):
        connector = MagicMock()
        connector.execute_query.return_value = []
        loader = create_baseline_loader(
            connector=connector,
            database="DB",
            baseline_dir=tmp_path / "baselines",
            baseline_stage=None,
            project_root=tmp_path,
        )
        assert isinstance(loader, DiskBaselineLoader)

    def test_returns_preloaded_loader_when_stage_has_data(self, tmp_path: Path):
        connector = MagicMock()
        connector.execute_query.return_value = [{
            "PROCEDURE_NAME": "dbo.P",
            "PARAMS": "{}",
            "CAPTURED_AT": "2026-01-01",
            "SUCCESS": True,
            "ERROR": None,
        }]
        loader = create_baseline_loader(
            connector=connector,
            database="DB",
            baseline_dir=None,
            baseline_stage=None,
            project_root=tmp_path,
        )
        assert isinstance(loader, _PreloadedLoader)

    def test_default_baseline_dir_when_none(self, tmp_path: Path):
        loader = create_baseline_loader(
            connector=None,
            database="DB",
            baseline_dir=None,
            baseline_stage=None,
            project_root=tmp_path,
        )
        assert isinstance(loader, DiskBaselineLoader)
        assert ".scai" in loader.source_label
