"""Tests for the telemetry module."""

import importlib
import importlib.util
from pathlib import Path
from unittest.mock import patch, MagicMock

import pytest

if importlib.util.find_spec("gjalla_precommit") is None:  # pragma: no cover
    pytest.skip("gjalla_precommit package not available", allow_module_level=True)

telemetry_module = importlib.import_module("gjalla_precommit.telemetry")
cli_module = importlib.import_module("gjalla_precommit.cli")


class TestIsEnabled:
    """Tests for _is_enabled()."""

    def test_enabled_via_config(self, home_dir):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("telemetry: true\n")
        assert telemetry_module._is_enabled() is True

    def test_disabled_via_config(self, home_dir):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("telemetry: false\n")
        assert telemetry_module._is_enabled() is False

    def test_disabled_when_not_set(self, home_dir):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("api_key: test\n")
        assert telemetry_module._is_enabled() is False

    def test_env_var_overrides_config_to_disable(self, home_dir, monkeypatch):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("telemetry: true\n")
        monkeypatch.setenv("GJALLA_TELEMETRY", "0")
        assert telemetry_module._is_enabled() is False

    def test_env_var_overrides_config_to_enable(self, home_dir, monkeypatch):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("telemetry: false\n")
        monkeypatch.setenv("GJALLA_TELEMETRY", "1")
        assert telemetry_module._is_enabled() is True

    def test_env_var_false_values(self, home_dir, monkeypatch):
        for val in ("0", "false", "no", "off"):
            monkeypatch.setenv("GJALLA_TELEMETRY", val)
            assert telemetry_module._is_enabled() is False


class TestGetInstallId:
    """Tests for _get_install_id()."""

    def test_creates_and_persists_uuid(self, home_dir):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("api_key: test\n")

        install_id = telemetry_module._get_install_id()
        assert len(install_id) == 36  # UUID format
        assert "-" in install_id

        # Second call returns same ID
        assert telemetry_module._get_install_id() == install_id

    def test_reads_existing_id(self, home_dir):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("install_id: my-fixed-id\n")

        assert telemetry_module._get_install_id() == "my-fixed-id"


class TestTrack:
    """Tests for track()."""

    def test_noop_when_disabled(self, home_dir):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("telemetry: false\n")

        with patch("gjalla_precommit.telemetry.httpx") as mock_httpx:
            telemetry_module.track("init")
            mock_httpx.post.assert_not_called()

    def test_sends_when_enabled(self, home_dir):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("telemetry: true\ninstall_id: test-id\n")

        with patch("gjalla_precommit.telemetry.httpx") as mock_httpx:
            telemetry_module.track("status")
            # Give daemon thread a moment to start
            import time
            time.sleep(0.1)
            mock_httpx.post.assert_called_once()
            call_kwargs = mock_httpx.post.call_args
            payload = call_kwargs.kwargs.get("json") or call_kwargs[1].get("json")
            assert payload["command"] == "status"
            assert payload["install_id"] == "test-id"

    def test_no_raise_on_network_failure(self, home_dir):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("telemetry: true\ninstall_id: test-id\n")

        with patch("gjalla_precommit.telemetry.httpx") as mock_httpx:
            mock_httpx.post.side_effect = Exception("network error")
            # Should not raise
            telemetry_module.track("sync")
            import time
            time.sleep(0.1)


class TestCheckConsent:
    """Tests for check_consent()."""

    def test_skips_when_env_var_set(self, home_dir, monkeypatch):
        monkeypatch.setenv("GJALLA_TELEMETRY", "0")
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("api_key: test\n")

        with patch("gjalla_precommit.telemetry.Confirm") as mock_confirm:
            telemetry_module.check_consent()
            mock_confirm.ask.assert_not_called()

    def test_skips_when_already_decided(self, home_dir, monkeypatch):
        monkeypatch.delenv("GJALLA_TELEMETRY", raising=False)
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("telemetry: false\n")

        with patch("gjalla_precommit.telemetry.Confirm") as mock_confirm:
            telemetry_module.check_consent()
            mock_confirm.ask.assert_not_called()

    def test_skips_when_not_a_tty(self, home_dir, monkeypatch):
        monkeypatch.delenv("GJALLA_TELEMETRY", raising=False)
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("api_key: test\n")

        with patch("gjalla_precommit.telemetry.sys") as mock_sys, \
             patch("gjalla_precommit.telemetry.Confirm") as mock_confirm:
            mock_sys.stdin.isatty.return_value = False
            # Need to keep os.environ accessible
            mock_sys.modules = {}
            telemetry_module.check_consent()
            mock_confirm.ask.assert_not_called()

        # Should NOT have persisted a decision
        import yaml
        cfg = yaml.safe_load((config_dir / "config.yaml").read_text())
        assert "telemetry" not in cfg

    def test_prompts_and_stores_decision(self, home_dir, monkeypatch):
        monkeypatch.delenv("GJALLA_TELEMETRY", raising=False)
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("api_key: test\n")

        with patch("gjalla_precommit.telemetry.sys") as mock_sys, \
             patch("gjalla_precommit.telemetry.Confirm") as mock_confirm:
            mock_sys.stdin.isatty.return_value = True
            mock_confirm.ask.return_value = True
            telemetry_module.check_consent()
            mock_confirm.ask.assert_called_once()

        # Verify stored
        import yaml
        cfg = yaml.safe_load((config_dir / "config.yaml").read_text())
        assert cfg["telemetry"] is True

    def test_handles_eof_gracefully(self, home_dir, monkeypatch):
        monkeypatch.delenv("GJALLA_TELEMETRY", raising=False)
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("api_key: test\n")

        with patch("gjalla_precommit.telemetry.sys") as mock_sys, \
             patch("gjalla_precommit.telemetry.Confirm") as mock_confirm:
            mock_sys.stdin.isatty.return_value = True
            mock_confirm.ask.side_effect = EOFError
            telemetry_module.check_consent()

        import yaml
        cfg = yaml.safe_load((config_dir / "config.yaml").read_text())
        assert cfg["telemetry"] is False


class TestInitSentry:
    """Tests for init_sentry()."""

    def test_noop_when_disabled(self, home_dir):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("telemetry: false\n")

        with patch("gjalla_precommit.telemetry.sentry_sdk", create=True) as mock_sdk:
            telemetry_module.init_sentry()
            mock_sdk.init.assert_not_called()

    def test_initializes_when_enabled(self, home_dir):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("telemetry: true\n")

        mock_sdk = MagicMock()
        with patch.dict("sys.modules", {"sentry_sdk": mock_sdk}):
            telemetry_module.init_sentry()
            mock_sdk.init.assert_called_once()
            call_kwargs = mock_sdk.init.call_args.kwargs
            assert call_kwargs["send_default_pii"] is False
            assert call_kwargs["include_local_variables"] is False
            assert call_kwargs["before_send"] is telemetry_module._sentry_before_send
            assert call_kwargs["dsn"] == telemetry_module.SENTRY_DSN

    def test_sets_tags(self, home_dir):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("telemetry: true\n")

        mock_sdk = MagicMock()
        with patch.dict("sys.modules", {"sentry_sdk": mock_sdk}):
            telemetry_module.init_sentry()
            tag_calls = {c.args[0]: c.args[1] for c in mock_sdk.set_tag.call_args_list}
            assert "service" in tag_calls
            assert tag_calls["service"] == "gjalla-cli"
            assert "os" in tag_calls
            assert "python_version" in tag_calls

    def test_silent_on_import_error(self, home_dir):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text("telemetry: true\n")

        with patch.dict("sys.modules", {"sentry_sdk": None}):
            # Should not raise
            telemetry_module.init_sentry()


class TestSentryBeforeSend:
    """Tests for _sentry_before_send()."""

    def test_scrubs_sensitive_keys(self):
        event = {
            "message": "test error",
            "extra": {
                "api_key": "sk-secret-123",
                "authorization": "Bearer token",
                "password": "hunter2",
                "normal_field": "safe",
            },
        }
        result = telemetry_module._sentry_before_send(event, {})
        assert result["extra"]["api_key"] == "[Filtered]"
        assert result["extra"]["authorization"] == "[Filtered]"
        assert result["extra"]["password"] == "[Filtered]"
        assert result["extra"]["normal_field"] == "safe"
        assert result["message"] == "test error"

    def test_scrubs_nested_dicts(self):
        event = {
            "contexts": {
                "runtime": {"name": "python"},
                "headers": {"authorization": "Bearer abc123"},
            },
        }
        result = telemetry_module._sentry_before_send(event, {})
        assert result["contexts"]["headers"]["authorization"] == "[Filtered]"
        assert result["contexts"]["runtime"]["name"] == "python"

    def test_scrubs_lists(self):
        event = {
            "breadcrumbs": [
                {"data": {"token": "leaked"}},
                {"data": {"url": "/api/status"}},
            ],
        }
        result = telemetry_module._sentry_before_send(event, {})
        assert result["breadcrumbs"][0]["data"]["token"] == "[Filtered]"
        assert result["breadcrumbs"][1]["data"]["url"] == "/api/status"

    def test_passes_through_plain_values(self):
        event = {"level": "error", "timestamp": 12345}
        result = telemetry_module._sentry_before_send(event, {})
        assert result == event


class TestTrackIntegration:
    """Integration test for command name tracking via CLI."""

    def test_tracks_correct_command_name(self, home_dir, monkeypatch):
        """result_callback should capture the invoked subcommand name."""
        monkeypatch.delenv("GJALLA_TELEMETRY", raising=False)
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text(
            "telemetry: true\ninstall_id: test-id\n"
        )

        from click.testing import CliRunner

        with patch("gjalla_precommit.telemetry.httpx") as mock_httpx:
            runner = CliRunner()
            result = runner.invoke(cli_module.main, ["status"])
            assert result.exit_code == 0

            import time
            time.sleep(0.1)

            mock_httpx.post.assert_called_once()
            payload = (
                mock_httpx.post.call_args.kwargs.get("json")
                or mock_httpx.post.call_args[1].get("json")
            )
            assert payload["command"] == "status"
