"""Tests for the init-derived CLI command.

Tests cover:
- Listing available derived tables
- Error handling for unsupported datasets
- BigQuery backend detection
- Database not found handling
- Successful materialization (mocked)
"""

from unittest.mock import patch

import pytest
from typer.testing import CliRunner

from m4.cli import app

runner = CliRunner()


@pytest.fixture(autouse=True)
def inject_version(monkeypatch):
    monkeypatch.setattr("m4.__version__", "0.0.1")


class TestInitDerivedList:
    """Tests for m4 init-derived --list."""

    @patch("m4.cli.get_active_backend", return_value="duckdb")
    def test_list_mimic_iv_shows_tables(self, mock_backend):
        result = runner.invoke(app, ["init-derived", "mimic-iv", "--list"])
        assert result.exit_code == 0
        assert "sofa" in result.stdout
        assert "sepsis3" in result.stdout
        assert "age" in result.stdout

    @patch("m4.cli.get_active_backend", return_value="duckdb")
    def test_list_mimic_iv_shows_count(self, mock_backend):
        result = runner.invoke(app, ["init-derived", "mimic-iv", "--list"])
        assert result.exit_code == 0
        assert "tables" in result.stdout


class TestInitDerivedErrors:
    """Tests for error handling in init-derived."""

    def test_unknown_dataset_fails(self):
        result = runner.invoke(app, ["init-derived", "nonexistent"])
        assert result.exit_code == 1
        assert "not supported" in result.stdout.lower() or "Not Found" in result.stdout

    def test_demo_dataset_blocked(self):
        result = runner.invoke(app, ["init-derived", "mimic-iv-demo"])
        assert result.exit_code == 1
        assert (
            "Not Supported" in result.stdout or "not supported" in result.stdout.lower()
        )

    @patch("m4.cli.get_active_backend", return_value="bigquery")
    def test_bigquery_backend_skips(self, mock_backend):
        result = runner.invoke(app, ["init-derived", "mimic-iv"])
        assert result.exit_code == 0
        assert "BigQuery" in result.stdout
        assert "already available" in result.stdout

    @patch("m4.cli.get_active_backend", return_value="duckdb")
    @patch("m4.cli.get_default_database_path", return_value=None)
    def test_missing_database_fails(self, mock_db_path, mock_backend):
        result = runner.invoke(app, ["init-derived", "mimic-iv"])
        assert result.exit_code == 1
        assert "Not Found" in result.stdout or "not found" in result.stdout.lower()

    @patch("m4.cli.get_active_backend", return_value="duckdb")
    def test_eicu_not_supported(self, mock_backend):
        result = runner.invoke(app, ["init-derived", "eicu"])
        assert result.exit_code == 1


class TestInitDerivedSkipForce:
    """Tests for skip/force behavior when derived tables already exist."""

    @patch("m4.cli.get_active_backend", return_value="duckdb")
    @patch("m4.cli.get_derived_table_count", return_value=42)
    @patch("m4.cli.materialize_all")
    @patch("m4.cli.get_default_database_path")
    def test_skips_when_derived_exist(
        self, mock_db_path, mock_materialize, mock_count, mock_backend, tmp_path
    ):
        db_file = tmp_path / "mimic_iv.duckdb"
        db_file.touch()
        mock_db_path.return_value = db_file

        result = runner.invoke(app, ["init-derived", "mimic-iv"])
        assert result.exit_code == 0
        assert "already materialized" in result.stdout
        assert "42 tables" in result.stdout
        mock_materialize.assert_not_called()

    @patch("m4.cli.get_active_backend", return_value="duckdb")
    @patch("m4.cli.get_derived_table_count", return_value=42)
    @patch("m4.cli.materialize_all")
    @patch("m4.cli.get_default_database_path")
    def test_force_rematerializes(
        self, mock_db_path, mock_materialize, mock_count, mock_backend, tmp_path
    ):
        db_file = tmp_path / "mimic_iv.duckdb"
        db_file.touch()
        mock_db_path.return_value = db_file
        mock_materialize.return_value = ["sofa", "sepsis3", "age"]

        result = runner.invoke(app, ["init-derived", "mimic-iv", "--force"])
        assert result.exit_code == 0
        mock_materialize.assert_called_once_with("mimic-iv", db_file)

    @patch("m4.cli.get_active_backend", return_value="duckdb")
    @patch("m4.cli.get_derived_table_count", return_value=0)
    @patch("m4.cli.materialize_all")
    @patch("m4.cli.get_default_database_path")
    def test_no_skip_when_no_derived(
        self, mock_db_path, mock_materialize, mock_count, mock_backend, tmp_path
    ):
        db_file = tmp_path / "mimic_iv.duckdb"
        db_file.touch()
        mock_db_path.return_value = db_file
        mock_materialize.return_value = ["sofa", "sepsis3", "age"]

        result = runner.invoke(app, ["init-derived", "mimic-iv"])
        assert result.exit_code == 0
        mock_materialize.assert_called_once_with("mimic-iv", db_file)


class TestInitDerivedMaterialize:
    """Tests for successful materialization (mocked)."""

    @patch("m4.cli.get_active_backend", return_value="duckdb")
    @patch("m4.cli.get_derived_table_count", return_value=0)
    @patch("m4.cli.materialize_all")
    @patch("m4.cli.get_default_database_path")
    def test_successful_materialization(
        self, mock_db_path, mock_materialize, mock_count, mock_backend, tmp_path
    ):
        db_file = tmp_path / "mimic_iv.duckdb"
        db_file.touch()
        mock_db_path.return_value = db_file
        mock_materialize.return_value = ["sofa", "sepsis3", "age"]

        result = runner.invoke(app, ["init-derived", "mimic-iv"])
        assert result.exit_code == 0
        mock_materialize.assert_called_once_with("mimic-iv", db_file)

    @patch("m4.cli.get_active_backend", return_value="duckdb")
    @patch("m4.cli.get_derived_table_count", return_value=0)
    @patch(
        "m4.cli.materialize_all", side_effect=ValueError("No built-in derived tables")
    )
    @patch("m4.cli.get_default_database_path")
    def test_value_error_handled(
        self, mock_db_path, mock_materialize, mock_count, mock_backend, tmp_path
    ):
        db_file = tmp_path / "mimic_iv.duckdb"
        db_file.touch()
        mock_db_path.return_value = db_file

        result = runner.invoke(app, ["init-derived", "mimic-iv"])
        assert result.exit_code == 1

    @patch("m4.cli.get_active_backend", return_value="duckdb")
    @patch("m4.cli.get_derived_table_count", return_value=0)
    @patch("m4.cli.materialize_all", side_effect=RuntimeError("SQL failed"))
    @patch("m4.cli.get_default_database_path")
    def test_runtime_error_handled(
        self, mock_db_path, mock_materialize, mock_count, mock_backend, tmp_path
    ):
        db_file = tmp_path / "mimic_iv.duckdb"
        db_file.touch()
        mock_db_path.return_value = db_file

        result = runner.invoke(app, ["init-derived", "mimic-iv"])
        assert result.exit_code == 1
        assert "failed" in result.stdout.lower()
