"""Tests for connection resolution in test_runner.common.config."""

from __future__ import annotations

import textwrap
from pathlib import Path

import pytest

from test_runner.common.config import load_config, ReplayConfiguration, ValidationConfiguration
from test_runner.common.database_dialect import DatabaseDialect


SQLSERVER_TOML = """\
default_connection_name = "default"

[connections.default]
host = "toml-host"
port = 1433
database = "toml_db"
username = "toml_user"
password = "toml_pw"
trust_server_certificate = "yes"
encrypt = "no"

[connections.staging]
host = "staging-host"
port = 2433
database = "staging_db"
username = "stg_user"
password = "stg_pw"
"""


def _write_config(project: Path, yaml_text: str) -> None:
    settings = project / "settings"
    settings.mkdir(parents=True, exist_ok=True)
    (settings / "test_config.yaml").write_text(textwrap.dedent(yaml_text))


def _write_toml(project: Path, content: str = SQLSERVER_TOML) -> None:
    toml_dir = project / ".snowflake" / "snowct"
    toml_dir.mkdir(parents=True, exist_ok=True)
    (toml_dir / "sqlserver.toml").write_text(content)


# ---------------------------------------------------------------------------
# Source connection resolved from TOML default
# ---------------------------------------------------------------------------

class TestSourceConnectionFromToml:
    def test_default_connection_resolves_from_toml(self, tmp_path: Path):
        _write_config(tmp_path, """\
            source_platform: sqlserver
        """)
        _write_toml(tmp_path)

        cfg = load_config(tmp_path)
        assert cfg.source_connection_raw["host"] == "toml-host"
        assert cfg.source_connection_raw["database"] == "toml_db"
        assert cfg.source_connection_raw["username"] == "toml_user"

    def test_no_toml_file_returns_empty_source(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
        home = tmp_path / "empty_home"
        home.mkdir()
        monkeypatch.setattr(Path, "home", staticmethod(lambda: home))

        _write_config(tmp_path, """\
            source_platform: sqlserver
        """)

        cfg = load_config(tmp_path)
        assert cfg.source_connection_raw == {}

    def test_toml_without_default_returns_empty_source(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
        home = tmp_path / "empty_home"
        home.mkdir()
        monkeypatch.setattr(Path, "home", staticmethod(lambda: home))

        _write_config(tmp_path, """\
            source_platform: sqlserver
        """)
        _write_toml(tmp_path, """\
[connections.staging]
host = "staging-host"
""")

        cfg = load_config(tmp_path)
        assert cfg.source_connection_raw == {}


# ---------------------------------------------------------------------------
# CLI --source-connection override
# ---------------------------------------------------------------------------

class TestSourceConnectionOverride:
    def test_cli_override_resolves_named_connection(self, tmp_path: Path):
        _write_config(tmp_path, """\
            source_platform: sqlserver
        """)
        _write_toml(tmp_path)

        cfg = load_config(tmp_path, source_connection="default")
        assert cfg.source_connection_raw["host"] == "toml-host"
        assert cfg.source_connection_raw["database"] == "toml_db"

    def test_cli_override_selects_specific_connection(self, tmp_path: Path):
        _write_config(tmp_path, """\
            source_platform: sqlserver
        """)
        _write_toml(tmp_path)

        cfg = load_config(tmp_path, source_connection="staging")
        assert cfg.source_connection_raw["host"] == "staging-host"
        assert cfg.source_connection_raw["database"] == "staging_db"

    def test_cli_override_missing_connection_raises(self, tmp_path: Path):
        _write_config(tmp_path, """\
            source_platform: sqlserver
        """)
        _write_toml(tmp_path)

        with pytest.raises(KeyError, match="nonexistent"):
            load_config(tmp_path, source_connection="nonexistent")


SNOWFLAKE_CONFIG_TOML = """\
default_connection_name = "my_sf"

[connections.my_sf]
account = "myaccount"
user = "myuser"
database = "mydb"
warehouse = "mywh"

[connections.other_sf]
account = "other-acct"
user = "other-user"
"""


def _write_sf_config(project: Path, content: str = SNOWFLAKE_CONFIG_TOML) -> None:
    sf_dir = project / ".snowflake"
    sf_dir.mkdir(parents=True, exist_ok=True)
    (sf_dir / "config.toml").write_text(content)


# ---------------------------------------------------------------------------
# Target connection resolved from TOML default
# ---------------------------------------------------------------------------

class TestTargetConnectionFromToml:
    def test_default_connection_resolves_from_toml(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
        home = tmp_path / "empty_home"
        home.mkdir()
        monkeypatch.setattr(Path, "home", staticmethod(lambda: home))

        _write_config(tmp_path, "source_platform: sqlserver\n")
        _write_sf_config(tmp_path)

        cfg = load_config(tmp_path)
        assert cfg.target_connection_raw["account"] == "myaccount"
        assert cfg.target_connection_raw["name"] == "my_sf"

    def test_no_config_toml_returns_empty_target(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
        home = tmp_path / "empty_home"
        home.mkdir()
        monkeypatch.setattr(Path, "home", staticmethod(lambda: home))

        _write_config(tmp_path, "source_platform: sqlserver\n")

        cfg = load_config(tmp_path)
        assert cfg.target_connection_raw == {}

    def test_config_toml_without_default_returns_empty(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
        home = tmp_path / "empty_home"
        home.mkdir()
        monkeypatch.setattr(Path, "home", staticmethod(lambda: home))

        _write_config(tmp_path, "source_platform: sqlserver\n")
        _write_sf_config(tmp_path, '[connections.other]\naccount = "x"\n')

        cfg = load_config(tmp_path)
        assert cfg.target_connection_raw == {}


# ---------------------------------------------------------------------------
# CLI --connection (target) override
# ---------------------------------------------------------------------------

class TestTargetConnectionOverride:
    def test_cli_override_sets_target_connection(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
        home = tmp_path / "empty_home"
        home.mkdir()
        monkeypatch.setattr(Path, "home", staticmethod(lambda: home))

        _write_config(tmp_path, "source_platform: sqlserver\n")

        cfg = load_config(tmp_path, target_connection="my_conn")
        assert cfg.target_connection_raw["name"] == "my_conn"
        assert cfg.target_connection_raw["mode"] == "name"

    def test_cli_override_takes_priority_over_toml(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
        home = tmp_path / "empty_home"
        home.mkdir()
        monkeypatch.setattr(Path, "home", staticmethod(lambda: home))

        _write_config(tmp_path, "source_platform: sqlserver\n")
        _write_sf_config(tmp_path)

        cfg = load_config(tmp_path, target_connection="override_conn")
        assert cfg.target_connection_raw["name"] == "override_conn"


# ---------------------------------------------------------------------------
# Legacy YAML source_connection/target_connection are ignored
# ---------------------------------------------------------------------------

class TestLegacyYamlConnectionsIgnored:
    """Verify that source_connection/target_connection in YAML are ignored.

    Old YAML files may still contain these keys from before the removal.
    load_config must not read them; connections come from TOML or CLI only.
    """

    def test_yaml_source_connection_is_ignored(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
        home = tmp_path / "empty_home"
        home.mkdir()
        monkeypatch.setattr(Path, "home", staticmethod(lambda: home))

        _write_config(tmp_path, """\
            source_platform: sqlserver
            source_connection:
              mode: credentials
              host: yaml-host
              database: yaml_db
        """)

        cfg = load_config(tmp_path)
        assert cfg.source_connection_raw == {}, \
            "source_connection in YAML must be ignored; connections come from TOML"

    def test_yaml_target_connection_is_ignored(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
        home = tmp_path / "empty_home"
        home.mkdir()
        monkeypatch.setattr(Path, "home", staticmethod(lambda: home))

        _write_config(tmp_path, """\
            source_platform: sqlserver
            target_connection:
              mode: name
              name: yaml-conn
        """)

        cfg = load_config(tmp_path)
        assert cfg.target_connection_raw == {}, \
            "target_connection in YAML must be ignored; connections come from TOML"


# ---------------------------------------------------------------------------
# in_memory_row_threshold
# ---------------------------------------------------------------------------

class TestInMemoryThreshold:
    def test_default_threshold_is_10000(self, tmp_path: Path):
        _write_config(tmp_path, """\
            source_platform: sqlserver
            validation_configuration:
              schema_validation: false
        """)
        cfg = load_config(tmp_path)
        assert cfg.validation_configuration is not None
        assert cfg.validation_configuration.in_memory_row_threshold == 10_000

    def test_custom_threshold_parsed(self, tmp_path: Path):
        _write_config(tmp_path, """\
            source_platform: sqlserver
            validation_configuration:
              in_memory_row_threshold: 500
        """)
        cfg = load_config(tmp_path)
        assert cfg.validation_configuration.in_memory_row_threshold == 500

    def test_zero_threshold_disables_in_memory(self, tmp_path: Path):
        _write_config(tmp_path, """\
            source_platform: sqlserver
            validation_configuration:
              in_memory_row_threshold: 0
        """)
        cfg = load_config(tmp_path)
        assert cfg.validation_configuration.in_memory_row_threshold == 0

    def test_default_on_dataclass_without_yaml(self):
        vc = ValidationConfiguration()
        assert vc.in_memory_row_threshold == 10_000


# ---------------------------------------------------------------------------
# tolerance and max_cell_diffs inside validation_configuration
# ---------------------------------------------------------------------------

class TestComparisonFields:
    def test_tolerance_default(self, tmp_path: Path):
        _write_config(tmp_path, """\
            source_platform: sqlserver
            validation_configuration:
              schema_validation: false
        """)
        cfg = load_config(tmp_path)
        assert cfg.validation_configuration.tolerance == 0.001

    def test_max_cell_diffs_default(self, tmp_path: Path):
        _write_config(tmp_path, """\
            source_platform: sqlserver
            validation_configuration:
              schema_validation: false
        """)
        cfg = load_config(tmp_path)
        assert cfg.validation_configuration.max_cell_diffs == 1_000

    def test_tolerance_parsed(self, tmp_path: Path):
        _write_config(tmp_path, """\
            source_platform: sqlserver
            validation_configuration:
              tolerance: 0.01
        """)
        cfg = load_config(tmp_path)
        assert cfg.validation_configuration.tolerance == 0.01

    def test_max_cell_diffs_parsed(self, tmp_path: Path):
        _write_config(tmp_path, """\
            source_platform: sqlserver
            validation_configuration:
              max_cell_diffs: 50
        """)
        cfg = load_config(tmp_path)
        assert cfg.validation_configuration.max_cell_diffs == 50

    def test_both_fields_parsed_together(self, tmp_path: Path):
        _write_config(tmp_path, """\
            source_platform: sqlserver
            validation_configuration:
              tolerance: 0.005
              max_cell_diffs: 200
        """)
        cfg = load_config(tmp_path)
        assert cfg.validation_configuration.tolerance == 0.005
        assert cfg.validation_configuration.max_cell_diffs == 200

    def test_defaults_on_dataclass(self):
        vc = ValidationConfiguration()
        assert vc.tolerance == 0.001
        assert vc.max_cell_diffs == 1_000


# ---------------------------------------------------------------------------
# Contract test: test-seed output is parseable by test-runner
#
# Both sides reference a single golden fixture file:
#   test-runner/tests/fixtures/seed_generated_test_config.yaml
#
# The C# snapshot test in TestFileGeneratorTests.cs asserts that
# test-seed's generator output exactly matches that file.  The Python
# tests below load the same file and assert test-runner parses it
# correctly.
#
# Failure chain when test-seed changes its output:
#   1. C# snapshot test fails → developer updates the golden file
#   2. Python tests below run against the new file → fail if test-runner
#      cannot parse the updated format, catching the mismatch early.
# ---------------------------------------------------------------------------

# Path to the golden fixture produced / verified by test-seed's snapshot test.
# Path: test-runner/tests/fixtures/seed_generated_test_config.yaml
_SEED_FIXTURE_PATH = Path(__file__).parents[1] / "fixtures" / "seed_generated_test_config.yaml"

class TestSeedConfigContract:
    """Contract test: verify test-runner can parse the YAML that test-seed generates.

    Both sides share a golden fixture file. If test-seed changes its output the C# snapshot
    test fails first, forcing the golden file update. This test then runs against the updated
    file, catching any parse failures on the test-runner side.

    The golden fixture no longer contains connection details — those are resolved at runtime
    from TOML files or CLI flags.
    """

    def test_seed_config_is_parseable_and_correct(self, tmp_path: Path) -> None:
        assert _SEED_FIXTURE_PATH.exists(), (
            f"Golden fixture missing: {_SEED_FIXTURE_PATH}\n"
            "Run the C# snapshot test (GenerateTestRunnerConfigAsync_YamlBodyMatchesGoldenFixture) "
            "to generate it."
        )
        _write_config(tmp_path, _SEED_FIXTURE_PATH.read_text())
        cfg = load_config(tmp_path)

        expected = ValidationConfiguration(
            schema_validation=False,
            metrics_validation=True,
            row_validation=True,
            max_failed_rows_number=100,
            tolerance=0.001,
            in_memory_row_threshold=10_000,
            max_cell_diffs=1_000,
        )
        assert cfg.source_dialect == DatabaseDialect.SQL_SERVER
        assert cfg.target_platform == "snowflake"
        assert cfg.validation_configuration == expected
        assert "comparison_configuration" not in cfg.raw

        expected_replay = ReplayConfiguration(
            small_table_threshold=100_000,
            large_table_threshold=10_000_000,
        )
        assert cfg.replay_configuration == expected_replay

        # Connection details are resolved from TOML at runtime, not from the
        # YAML fixture. Their values depend on the environment (e.g.
        # ~/.snowflake/config.toml) so we only verify the YAML itself does
        # not contain connection keys.
        assert "source_connection" not in cfg.raw
        assert "target_connection" not in cfg.raw
