"""Tests for CLI commands."""

import json
import pytest
from typer.testing import CliRunner

from opencypher_compliance.cli import app

runner = CliRunner()


class TestListCommand:
    def test_list_all(self):
        result = runner.invoke(app, ["list"])
        assert result.exit_code == 0
        assert "Catalog:" in result.output
        assert "clause" in result.output

    def test_list_filter_type(self):
        result = runner.invoke(app, ["list", "--types", "clause"])
        assert result.exit_code == 0
        assert "[clause]" in result.output
        assert "[operator]" not in result.output


class TestValidateCommand:
    def test_validate_builtin(self):
        result = runner.invoke(app, ["validate"])
        assert result.exit_code == 0
        assert "Catalog valid" in result.output

    def test_validate_bad_dir(self):
        result = runner.invoke(app, ["validate", "--catalog-dir", "/nonexistent"])
        assert result.exit_code == 1


class TestRunCommand:
    def test_run_no_config_or_adapter(self, tmp_path, monkeypatch):
        """Running without config or adapter should fail with helpful message."""
        monkeypatch.chdir(tmp_path)
        result = runner.invoke(app, ["run"])
        assert result.exit_code == 1
        assert "config.yaml" in result.output

    def test_run_missing_config_file(self):
        result = runner.invoke(app, ["run", "-c", "nonexistent_config.yaml"])
        assert result.exit_code == 1
        assert "Config file not found" in result.output

    def test_run_old_database_key_rejected(self, tmp_path):
        """Config files with the old database: key should be rejected."""
        import yaml
        config_data = {
            "database": {
                "name": "Old Format",
                "adapter": "bolt",
                "host": "localhost",
                "port": 7687,
            }
        }
        config_file = tmp_path / "old.yaml"
        config_file.write_text(yaml.dump(config_data))
        result = runner.invoke(app, ["run", "-c", str(config_file)])
        assert result.exit_code == 1
        assert "'databases' list" in result.output

    def test_run_unreachable_database(self, tmp_path, monkeypatch):
        import yaml
        import opencypher_compliance.runner as runner_mod
        from opencypher_compliance.adapters import CypherAdapter

        class FailAdapter(CypherAdapter):
            def connect(self, host, port, auth, **options):
                raise OSError("Connection refused")
            def execute(self, query):
                raise NotImplementedError
            def close(self):
                pass

        monkeypatch.setattr(runner_mod, "get_adapter", lambda name: FailAdapter())

        config_data = {
            "databases": [{
                "name": "Unreachable DB",
                "adapter": "bolt",
                "host": "localhost",
                "port": 19999,
                "auth": {"username": "neo4j", "password": "test"},
            }]
        }
        config_file = tmp_path / "config.yaml"
        config_file.write_text(yaml.dump(config_data))

        result = runner.invoke(app, ["run", "-c", str(config_file)])
        assert result.exit_code == 1
        assert "Cannot connect" in result.output

    def test_run_single_database_config(self, tmp_path, mock_adapter, monkeypatch):
        """run -c with a single-entry databases list works."""
        import yaml
        import opencypher_compliance.runner as runner_mod

        catalog_dir = tmp_path / "catalog"
        catalog_dir.mkdir()
        (catalog_dir / "test.yaml").write_text(yaml.dump({
            "tests": [{"name": "T1", "type": "clause", "query": "RETURN 1", "expect_success": True}]
        }))

        config_data = {
            "databases": [{
                "name": "SingleDB",
                "adapter": "bolt",
                "host": "localhost",
                "port": 7687,
                "auth": {"username": "neo4j", "password": "test"},
            }]
        }
        config_file = tmp_path / "config.yaml"
        config_file.write_text(yaml.dump(config_data))

        original_run = runner_mod.run_compliance

        def patched_run(**kwargs):
            kwargs["adapter_instance"] = mock_adapter
            return original_run(**kwargs)

        monkeypatch.setattr(runner_mod, "run_compliance", patched_run)

        out_dir = tmp_path / "results"
        result = runner.invoke(app, [
            "run", "-c", str(config_file),
            "-o", str(out_dir),
            "--catalog-dir", str(catalog_dir),
        ])
        assert result.exit_code == 0
        assert "Output written to" in result.output

    def test_run_multi_database_config(self, tmp_path, mock_adapter, monkeypatch):
        """run -c with multiple databases runs all of them."""
        import yaml
        import opencypher_compliance.runner as runner_mod

        catalog_dir = tmp_path / "catalog"
        catalog_dir.mkdir()
        (catalog_dir / "test.yaml").write_text(yaml.dump({
            "tests": [{"name": "T1", "type": "clause", "query": "RETURN 1", "expect_success": True}]
        }))

        multi = {
            "databases": [
                {"name": "DB-A", "adapter": "bolt", "host": "localhost", "port": 7687,
                 "auth": {"username": "neo4j", "password": "test"}},
                {"name": "DB-B", "adapter": "bolt", "host": "localhost", "port": 7688,
                 "auth": {"username": "neo4j", "password": "test"}},
            ]
        }
        config_file = tmp_path / "databases.yaml"
        config_file.write_text(yaml.dump(multi))

        original_run = runner_mod.run_compliance

        def patched_run(**kwargs):
            kwargs["adapter_instance"] = mock_adapter
            return original_run(**kwargs)

        monkeypatch.setattr(runner_mod, "run_compliance", patched_run)

        out_dir = tmp_path / "results"
        result = runner.invoke(app, [
            "run", "-c", str(config_file),
            "-o", str(out_dir),
            "--catalog-dir", str(catalog_dir),
        ])
        assert result.exit_code == 0
        assert "2 succeeded, 0 failed" in result.output
        json_files = list(out_dir.glob("*.json"))
        assert len(json_files) == 2

    def test_run_inline_flags(self, mock_adapter, tmp_path, monkeypatch):
        """run with inline flags still works for single database."""
        import yaml
        import opencypher_compliance.runner as runner_mod

        catalog_dir = tmp_path / "catalog"
        catalog_dir.mkdir()
        (catalog_dir / "test.yaml").write_text(yaml.dump({
            "tests": [{"name": "T1", "type": "clause", "query": "RETURN 1", "expect_success": True}]
        }))

        original_run = runner_mod.run_compliance

        def patched_run(**kwargs):
            kwargs["adapter_instance"] = mock_adapter
            return original_run(**kwargs)

        monkeypatch.setattr(runner_mod, "run_compliance", patched_run)

        out_dir = tmp_path / "results"
        result = runner.invoke(app, [
            "run", "-a", "bolt", "--host", "localhost", "--port", "7687",
            "-u", "neo4j", "-p", "test",
            "-o", str(out_dir),
            "--catalog-dir", str(catalog_dir),
        ])
        assert result.exit_code == 0
        assert "Output written to" in result.output

    def test_quiet_mode_inline_flags(self, mock_adapter, sample_config, tmp_path, monkeypatch):
        """--quiet works with inline flags."""
        import yaml
        import opencypher_compliance.runner as runner_mod

        catalog = {
            "tests": [
                {"name": "T1", "type": "clause", "query": "RETURN 1", "expect_success": True}
            ]
        }
        catalog_path = tmp_path / "catalog"
        catalog_path.mkdir()
        (catalog_path / "test.yaml").write_text(yaml.dump(catalog))

        original_run = runner_mod.run_compliance

        def patched_run(**kwargs):
            kwargs["adapter_instance"] = mock_adapter
            return original_run(**kwargs)

        monkeypatch.setattr(runner_mod, "run_compliance", patched_run)

        result = runner.invoke(app, [
            "run", "-a", "bolt", "--host", "localhost", "--port", "7687",
            "-u", "neo4j", "-p", "test",
            "--catalog-dir", str(catalog_path),
            "--quiet",
        ])
        assert result.exit_code == 0
        parsed = json.loads(result.output)
        assert "metadata" in parsed
        assert parsed["metadata"]["total_tests"] == 1

    def test_run_with_report_flag(self, tmp_path, mock_adapter, monkeypatch):
        """run -c config.yaml -r generates an HTML report after running."""
        import yaml
        import webbrowser
        import opencypher_compliance.runner as runner_mod

        catalog_dir = tmp_path / "catalog"
        catalog_dir.mkdir()
        (catalog_dir / "test.yaml").write_text(yaml.dump({
            "tests": [{"name": "T1", "type": "clause", "query": "RETURN 1", "expect_success": True}]
        }))

        config_data = {
            "databases": [{
                "name": "TestDB",
                "adapter": "bolt",
                "host": "localhost",
                "port": 7687,
                "auth": {"username": "neo4j", "password": "test"},
            }]
        }
        config_file = tmp_path / "config.yaml"
        config_file.write_text(yaml.dump(config_data))

        original_run = runner_mod.run_compliance

        def patched_run(**kwargs):
            kwargs["adapter_instance"] = mock_adapter
            return original_run(**kwargs)

        monkeypatch.setattr(runner_mod, "run_compliance", patched_run)
        monkeypatch.setattr(webbrowser, "open", lambda url: None)

        out_dir = tmp_path / "results"
        result = runner.invoke(app, [
            "run", "-c", str(config_file),
            "-o", str(out_dir),
            "--catalog-dir", str(catalog_dir),
            "-r",
        ])
        assert result.exit_code == 0
        assert "Report written to" in result.output
        assert (out_dir / "report.html").exists()

    def test_quiet_mode_rejected_with_config_file(self, tmp_path):
        """--quiet is rejected when using -c."""
        import yaml
        config_data = {
            "databases": [{"name": "DB", "adapter": "bolt", "host": "localhost", "port": 7687}]
        }
        config_file = tmp_path / "config.yaml"
        config_file.write_text(yaml.dump(config_data))
        result = runner.invoke(app, ["run", "-c", str(config_file), "--quiet"])
        assert result.exit_code == 1
        assert "--quiet is not supported" in result.output


class TestReportCommand:
    def _make_result_json(self, db_name="TestDB"):
        return json.dumps({
            "metadata": {
                "database": db_name,
                "total_tests": 2,
                "passed": 1,
                "failed": 1,
                "pass_rate": "50%",
                "timestamp": "2026-03-14T00:00:00",
            },
            "results": [
                {"element": "MATCH", "type": "clause", "result": "pass", "duration_ms": 10},
                {"element": "PLUS", "type": "operator", "result": "fail", "error": "Unsupported", "duration_ms": 5},
            ],
            "summary_by_type": {},
        })

    def test_report_no_results(self, tmp_path):
        result = runner.invoke(app, ["report", "-i", str(tmp_path), "--no-open"])
        assert result.exit_code == 1
        assert "No valid JSON result files" in result.output

    def test_report_nonexistent_dir(self, tmp_path):
        bad_path = str(tmp_path / "nonexistent")
        result = runner.invoke(app, ["report", "-i", bad_path, "--no-open"])
        assert result.exit_code == 1

    def test_report_generates_html(self, tmp_path):
        (tmp_path / "input").mkdir()
        (tmp_path / "input" / "db.json").write_text(self._make_result_json())
        out = tmp_path / "output"
        result = runner.invoke(app, [
            "report",
            "-i", str(tmp_path / "input"),
            "-o", str(out),
            "--no-open",
        ])
        assert result.exit_code == 0
        assert "Report written to" in result.output
        assert (out / "report.html").exists()


class TestVersionFlag:
    def test_version(self):
        result = runner.invoke(app, ["--version"])
        assert result.exit_code == 0
        assert "opencypher-compliance v" in result.output
