"""Tests for the init command."""

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

import pytest
from click.testing import CliRunner

init_module = importlib.import_module("gjalla_precommit.commands.init")
api_module = importlib.import_module("gjalla_precommit.commands._api")
cli_module = importlib.import_module("gjalla_precommit.cli")


class TestInitCommand:
    """Tests for gjalla init command."""

    def test_init_command_exists(self):
        """Init command should be registered."""
        main = cli_module.main
        assert "init" in main.commands

    def test_init_not_in_git_repo(self, tmp_path, monkeypatch):
        """Init should fail when not in a git repo."""
        monkeypatch.chdir(tmp_path)
        runner = CliRunner()
        result = runner.invoke(cli_module.main, ["init"])
        assert result.exit_code != 0
        assert "git repository" in result.output.lower()

    def test_init_help_flag(self):
        """Init --help should show help text."""
        runner = CliRunner()
        result = runner.invoke(cli_module.main, ["init", "--help"])
        assert result.exit_code == 0
        assert "--api-key" in result.output
        assert "--project-id" in result.output

    def test_init_api_key_from_env(self, temp_repo, home_dir, monkeypatch):
        """Init should use API key from GJALLA_API_KEY env var."""
        monkeypatch.chdir(temp_repo)
        monkeypatch.setenv("GJALLA_API_KEY", "gj_env_test_key")

        # Mock API verification to fail (we don't want actual API calls)
        with patch.object(init_module, "verify_api_key", return_value=(False, "API key was rejected by the server (HTTP 401 Unauthorized). API keys must have explicit access to projects — you can configure project access in the API key tab.")):
            runner = CliRunner()
            result = runner.invoke(cli_module.main, ["init"])
            # Should attempt to verify the env var key
            assert result.exit_code != 0
            # The API key was used (verified but failed)
            assert "API key verification failed" in result.output or "API key" in result.output


class TestLoadGlobalConfig:
    """Tests for load_global_config function."""

    def test_load_config_no_file(self, home_dir, monkeypatch):
        """Should return defaults when no config file exists."""
        config = init_module.load_global_config()
        assert config.get("api_key") is None
        assert config.get("api_url") == "https://gjalla.io"
        assert config.get("projects") == {}

    def test_load_config_with_file(self, home_dir, monkeypatch):
        """Should load config from file."""
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        config_path = config_dir / "config.yaml"
        config_path.write_text("api_key: gj_test\napi_url: https://custom.api\n")

        config = init_module.load_global_config()
        assert config.get("api_key") == "gj_test"
        assert config.get("api_url") == "https://custom.api"


class TestSaveGlobalConfig:
    """Tests for save_global_config function."""

    def test_save_config_creates_directory(self, home_dir, monkeypatch):
        """Should create .gjalla directory if it doesn't exist."""
        gjalla_dir = home_dir / ".gjalla"
        config_path = gjalla_dir / "config.yaml"

        config = {"api_key": "gj_test", "api_url": "https://gjalla.io"}
        init_module.save_global_config(config)

        assert gjalla_dir.exists()
        assert config_path.exists()

    def test_save_config_writes_yaml(self, home_dir, monkeypatch):
        """Should write valid YAML to config file."""
        gjalla_dir = home_dir / ".gjalla"
        config_path = gjalla_dir / "config.yaml"

        config = {"api_key": "gj_test", "projects": {"/path/to/repo": "123"}}
        init_module.save_global_config(config)

        import yaml
        loaded = yaml.safe_load(config_path.read_text())
        assert loaded["api_key"] == "gj_test"
        assert loaded["projects"]["/path/to/repo"] == "123"


class TestVerifyApiKey:
    """Tests for verify_api_key function."""

    def test_verify_api_key_success(self):
        """Should return (True, '') for valid API key."""
        with patch("httpx.get") as mock_get:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_get.return_value = mock_response

            ok, detail = api_module.verify_api_key("gj_valid", "https://api.gjalla.io")
            assert ok is True
            assert detail == ""

    def test_verify_api_key_invalid(self):
        """Should return (False, detail) for 401 response."""
        with patch("httpx.get") as mock_get:
            mock_response = MagicMock()
            mock_response.status_code = 401
            mock_get.return_value = mock_response

            ok, detail = api_module.verify_api_key("gj_invalid", "https://api.gjalla.io")
            assert ok is False
            assert "401" in detail

    def test_verify_api_key_network_error(self):
        """Should return (False, detail) on network error."""
        import httpx
        with patch("httpx.get", side_effect=httpx.ConnectError("Connection refused")):
            ok, detail = api_module.verify_api_key("gj_test", "https://api.gjalla.io")
            assert ok is False
            assert "Could not connect" in detail

    def test_verify_api_key_timeout(self):
        """Should return (False, detail) on timeout."""
        import httpx
        with patch("httpx.get", side_effect=httpx.TimeoutException("timed out")):
            ok, detail = api_module.verify_api_key("gj_test", "https://api.gjalla.io")
            assert ok is False
            assert "timed out" in detail


class TestListProjects:
    """Tests for list_projects function."""

    def test_list_projects_success(self):
        """Should return list of projects."""
        with patch("httpx.get") as mock_get:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.json.return_value = {
                "projects": [
                    {"id": 1, "title": "Project 1", "description": "Desc 1"},
                    {"id": 2, "title": "Project 2", "description": "Desc 2"},
                ]
            }
            mock_get.return_value = mock_response

            result = api_module.list_projects("gj_test", "https://api.gjalla.io")
            assert len(result) == 2
            assert result[0]["title"] == "Project 1"

    def test_list_projects_empty(self):
        """Should return empty list when no projects."""
        with patch("httpx.get") as mock_get:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.json.return_value = {"projects": []}
            mock_get.return_value = mock_response

            result = api_module.list_projects("gj_test", "https://api.gjalla.io")
            assert result == []

    def test_list_projects_error(self):
        """Should return None on API error."""
        with patch("httpx.get") as mock_get:
            mock_response = MagicMock()
            mock_response.status_code = 500
            mock_get.return_value = mock_response

            result = api_module.list_projects("gj_test", "https://api.gjalla.io")
            assert result is None

    def test_list_projects_invalid_json(self):
        """Should return None on invalid JSON."""
        with patch("httpx.get") as mock_get:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.json.side_effect = json.JSONDecodeError("bad", "doc", 1)
            mock_get.return_value = mock_response

            result = api_module.list_projects("gj_test", "https://api.gjalla.io")
            assert result is None


class TestDiscoverProject:
    """Tests for discover_project function."""

    def test_discover_project_ssh_url(self):
        """Should extract repo name from SSH URL."""
        with patch.object(api_module, "list_projects") as mock_list:
            mock_list.return_value = [
                {"id": 1, "title": "my-repo", "description": ""},
            ]

            result = api_module.discover_project(
                "git@github.com:org/my-repo.git",
                "gj_test",
                "https://api.gjalla.io"
            )
            assert result is not None
            assert result["id"] == 1
            assert result["name"] == "my-repo"

    def test_discover_project_https_url(self):
        """Should extract repo name from HTTPS URL."""
        with patch.object(api_module, "list_projects") as mock_list:
            mock_list.return_value = [
                {"id": 2, "title": "another-repo", "description": ""},
            ]

            result = api_module.discover_project(
                "https://github.com/org/another-repo.git",
                "gj_test",
                "https://api.gjalla.io"
            )
            assert result is not None
            assert result["id"] == 2

    def test_discover_project_partial_match(self):
        """Should match by partial name if exact match not found."""
        with patch.object(api_module, "list_projects") as mock_list:
            mock_list.return_value = [
                {"id": 3, "title": "prefix-my-repo-suffix", "description": ""},
            ]

            result = api_module.discover_project(
                "git@github.com:org/my-repo.git",
                "gj_test",
                "https://api.gjalla.io"
            )
            assert result is not None
            assert result["id"] == 3

    def test_discover_project_not_found(self):
        """Should return None when no match."""
        with patch.object(api_module, "list_projects") as mock_list:
            mock_list.return_value = [
                {"id": 1, "title": "unrelated", "description": ""},
            ]

            result = api_module.discover_project(
                "git@github.com:org/my-repo.git",
                "gj_test",
                "https://api.gjalla.io"
            )
            assert result is None

    def test_discover_project_unrecognized_remote(self):
        """Should return None when remote URL can't be parsed."""
        with patch.object(api_module, "list_projects") as mock_list:
            result = api_module.discover_project(
                "ssh://example.com/repo",
                "gj_test",
                "https://api.gjalla.io"
            )
            assert result is None
            mock_list.assert_not_called()


class TestMakeApiHeaders:
    """Tests for _make_api_headers helper."""

    def test_make_api_headers(self):
        headers = api_module._make_api_headers("gj_test")
        assert headers["x-api-key"] == "gj_test"
        assert headers["Content-Type"] == "application/json"
        assert headers["Accept"] == "application/json"


class TestSelectProjectInteractively:
    """Tests for select_project_interactively function."""

    def test_select_existing_project(self):
        """Should return existing project when selected."""
        with patch.object(api_module, "list_projects") as mock_list:
            mock_list.return_value = [
                {"id": 1, "title": "Project 1", "description": "Desc 1"},
                {"id": 2, "title": "Project 2", "description": "Desc 2"},
            ]
            with patch("rich.prompt.Prompt.ask", return_value="1"):
                result = api_module.select_project_interactively("gj_test", "https://api.gjalla.io")
                assert result is not None
                assert result["id"] == 1
                assert result["name"] == "Project 1"

    def test_no_projects_returns_none(self):
        """Should return None and show message when no projects exist."""
        with patch.object(api_module, "list_projects") as mock_list:
            mock_list.return_value = []
            result = api_module.select_project_interactively("gj_test", "https://api.gjalla.io")
            assert result is None

    def test_select_invalid_choice(self):
        """Should return None for invalid selection."""
        with patch.object(api_module, "list_projects") as mock_list:
            mock_list.return_value = [
                {"id": 1, "title": "Project 1", "description": ""},
            ]
            with patch("rich.prompt.Prompt.ask", return_value="99"):
                result = api_module.select_project_interactively("gj_test", "https://api.gjalla.io")
                assert result is None


class TestInitOfflineMode:
    """Tests for offline init (no API key)."""

    def test_offline_init_creates_config_with_null_project(self, temp_repo, home_dir, monkeypatch):
        """Init without API key creates .gjalla/config.yaml with project_id: null."""
        monkeypatch.chdir(temp_repo)
        monkeypatch.delenv("GJALLA_API_KEY", raising=False)
        runner = CliRunner()

        with patch.object(init_module, "load_global_config", return_value={"api_url": "https://gjalla.io", "projects": {}}), \
             patch.object(init_module, "save_global_config"), \
             patch.object(init_module, "is_git_repo", return_value=True), \
             patch.object(init_module, "Confirm") as mock_confirm, \
             patch.object(init_module, "Prompt") as mock_prompt:
            mock_confirm.ask.return_value = False
            mock_prompt.ask.return_value = ""
            result = runner.invoke(cli_module.main, ["init"])

        assert result.exit_code == 0, result.output
        gjalla_config = temp_repo / ".gjalla" / "config.yaml"
        assert gjalla_config.exists()
        import yaml
        data = yaml.safe_load(gjalla_config.read_text())
        assert data["project_id"] is None
        assert "local-only" in result.output.lower()
        assert "gjalla connect" in result.output


class TestInitFlow:
    """Integration-style tests for init command flow branches."""

    def test_init_uses_config_api_key_with_project_id(self, temp_repo, monkeypatch):
        """Should use API key from config and skip discovery when project ID provided."""
        monkeypatch.chdir(temp_repo)
        runner = CliRunner()

        config = {"api_key": "gj_config_key", "api_url": "https://gjalla.io", "projects": {}}

        with patch.object(init_module, "load_global_config", return_value=config), \
            patch.object(init_module, "save_global_config") as save_config, \
            patch.object(init_module, "verify_api_key", return_value=(True, "")) as verify_mock, \
            patch.object(init_module, "is_git_repo", return_value=True), \
            patch.object(init_module, "Confirm") as mock_confirm:
            mock_confirm.ask.return_value = False
            result = runner.invoke(cli_module.main, ["init", "--project-id", "123"])

        assert result.exit_code == 0
        verify_mock.assert_called_once_with("gj_config_key", "https://gjalla.io")
        saved_config = save_config.call_args[0][0]
        assert saved_config["projects"][str(temp_repo)] == "123"

    def test_init_creates_gjalla_config(self, temp_repo, monkeypatch):
        """Should create .gjalla project config file."""
        monkeypatch.chdir(temp_repo)
        runner = CliRunner()

        config = {"api_key": "gj_test_key", "api_url": "https://gjalla.io", "projects": {}}

        with patch.object(init_module, "load_global_config", return_value=config), \
            patch.object(init_module, "save_global_config"), \
            patch.object(init_module, "verify_api_key", return_value=(True, "")), \
            patch.object(init_module, "is_git_repo", return_value=True), \
            patch.object(init_module, "Confirm") as mock_confirm:
            mock_confirm.ask.return_value = False
            result = runner.invoke(cli_module.main, ["init", "--project-id", "42"])

        assert result.exit_code == 0
        gjalla_config = temp_repo / ".gjalla" / "config.yaml"
        assert gjalla_config.exists()
        import yaml
        data = yaml.safe_load(gjalla_config.read_text())
        assert data["project_id"] == 42
        assert data["api_url"] == "https://gjalla.io"


class TestInitApiFallback:
    """Tests for API key fallback paths when global key fails."""

    def _run_init_with_fallback(self, temp_repo, monkeypatch, runner, *, local_config=None, mcp_config=None, env_vars=None):
        """Helper: run init with a failing global key and optional local sources."""
        monkeypatch.chdir(temp_repo)
        monkeypatch.delenv("GJALLA_API_KEY", raising=False)
        monkeypatch.delenv("GJALLA_API_URL", raising=False)

        if env_vars:
            for k, v in env_vars.items():
                monkeypatch.setenv(k, v)

        # Set up .gjalla/config.yaml
        if local_config is not None:
            import yaml as _yaml
            gjalla_dir = temp_repo / ".gjalla"
            gjalla_dir.mkdir(parents=True, exist_ok=True)
            (gjalla_dir / "config.yaml").write_text(_yaml.dump(local_config, default_flow_style=False))

        # Set up .mcp.json
        if mcp_config is not None:
            (temp_repo / ".mcp.json").write_text(json.dumps(mcp_config))

        global_config = {"api_key": "gj_stale_global", "api_url": "https://gjalla.io", "projects": {}}

        def mock_verify(key, url):
            if key == "gj_stale_global":
                return (False, "HTTP 401 Unauthorized")
            if key == "gj_good_local":
                return (True, "")
            if key == "gj_good_mcp":
                return (True, "")
            return (False, "unknown key")

        with patch.object(init_module, "load_global_config", return_value=global_config), \
             patch.object(init_module, "save_global_config"), \
             patch.object(init_module, "verify_api_key", side_effect=mock_verify), \
             patch.object(init_module, "is_git_repo", return_value=True), \
             patch.object(init_module, "Confirm") as mock_confirm:
            mock_confirm.ask.return_value = False
            return runner.invoke(cli_module.main, ["init", "--project-id", "99"])

    def test_fallback_api_key_env_succeeds(self, temp_repo, monkeypatch):
        """Global key fails -> api_key_env resolves from environment -> succeeds."""
        monkeypatch.chdir(temp_repo)
        monkeypatch.delenv("GJALLA_API_KEY", raising=False)
        monkeypatch.delenv("GJALLA_API_URL", raising=False)
        # Use a custom env var name to avoid Click's envvar="GJALLA_API_KEY"
        # picking it up directly (which would bypass the fallback entirely).
        monkeypatch.setenv("GJALLA_PROJECT_KEY", "gj_good_local")

        gjalla_dir = temp_repo / ".gjalla"
        gjalla_dir.mkdir(parents=True, exist_ok=True)
        import yaml as _yaml
        (gjalla_dir / "config.yaml").write_text(_yaml.dump({
            "api_key_env": "GJALLA_PROJECT_KEY",
            "api_url": "https://gjalla.io",
        }, default_flow_style=False))

        global_config = {"api_key": "gj_stale_global", "api_url": "https://gjalla.io", "projects": {}}
        verify_calls = []

        def mock_verify(key, url):
            verify_calls.append(key)
            if key == "gj_stale_global":
                return (False, "HTTP 401 Unauthorized")
            if key == "gj_good_local":
                return (True, "")
            return (False, "unknown key")

        runner = CliRunner()
        with patch.object(init_module, "load_global_config", return_value=global_config), \
             patch.object(init_module, "save_global_config"), \
             patch.object(init_module, "verify_api_key", side_effect=mock_verify), \
             patch.object(init_module, "is_git_repo", return_value=True), \
             patch.object(init_module, "Confirm") as mock_confirm:
            mock_confirm.ask.return_value = False
            result = runner.invoke(cli_module.main, ["init", "--project-id", "99"])

        assert result.exit_code == 0, result.output
        assert "API key verified" in result.output
        # Verify the fallback was actually used (not just Click picking up the env var)
        assert verify_calls == ["gj_stale_global", "gj_good_local"]
        assert "GJALLA_PROJECT_KEY" in result.output  # fallback info message

    def test_fallback_mcp_json_succeeds(self, temp_repo, monkeypatch):
        """Global key fails -> .mcp.json gjalla entry has key -> succeeds."""
        runner = CliRunner()
        result = self._run_init_with_fallback(
            temp_repo, monkeypatch, runner,
            mcp_config={"mcpServers": {"gjalla": {"command": "npx", "args": [], "env": {"GJALLA_API_KEY": "gj_good_mcp"}}}},
        )
        assert result.exit_code == 0, result.output
        assert "API key verified" in result.output

    def test_fallback_mcp_json_uses_base_url(self, temp_repo, monkeypatch):
        """Global key fails -> .mcp.json has GJALLA_BASE_URL -> uses that URL."""
        monkeypatch.chdir(temp_repo)
        monkeypatch.delenv("GJALLA_API_KEY", raising=False)
        monkeypatch.delenv("GJALLA_API_URL", raising=False)

        (temp_repo / ".mcp.json").write_text(json.dumps({
            "mcpServers": {"gjalla": {"command": "npx", "args": [], "env": {
                "GJALLA_API_KEY": "gj_good_mcp",
                "GJALLA_BASE_URL": "https://custom.gjalla.io",
            }}}
        }))

        global_config = {"api_key": "gj_stale_global", "api_url": "https://gjalla.io", "projects": {}}
        verify_calls = []

        def mock_verify(key, url):
            verify_calls.append((key, url))
            if key == "gj_stale_global":
                return (False, "HTTP 401 Unauthorized")
            if key == "gj_good_mcp":
                return (True, "")
            return (False, "unknown key")

        runner = CliRunner()
        with patch.object(init_module, "load_global_config", return_value=global_config), \
             patch.object(init_module, "save_global_config"), \
             patch.object(init_module, "verify_api_key", side_effect=mock_verify), \
             patch.object(init_module, "is_git_repo", return_value=True), \
             patch.object(init_module, "Confirm") as mock_confirm:
            mock_confirm.ask.return_value = False
            result = runner.invoke(cli_module.main, ["init", "--project-id", "99"])

        assert result.exit_code == 0, result.output
        # Verify the MCP key was tried with the custom URL
        mcp_call = [c for c in verify_calls if c[0] == "gj_good_mcp"]
        assert len(mcp_call) == 1
        assert mcp_call[0][1] == "https://custom.gjalla.io"

    def test_fallback_no_local_sources_fails(self, temp_repo, monkeypatch):
        """Global key fails -> no local sources -> fails as before."""
        runner = CliRunner()
        result = self._run_init_with_fallback(
            temp_repo, monkeypatch, runner,
        )
        assert result.exit_code != 0
        assert "API key verification failed" in result.output

    def test_fallback_api_key_env_unset_falls_through_to_mcp(self, temp_repo, monkeypatch):
        """Global key fails -> api_key_env points to unset var -> falls through to .mcp.json."""
        runner = CliRunner()
        result = self._run_init_with_fallback(
            temp_repo, monkeypatch, runner,
            local_config={"api_key_env": "GJALLA_UNSET_VAR", "api_url": "https://gjalla.io"},
            mcp_config={"mcpServers": {"gjalla": {"command": "npx", "args": [], "env": {"GJALLA_API_KEY": "gj_good_mcp"}}}},
        )
        assert result.exit_code == 0, result.output
        assert "API key verified" in result.output
