"""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")
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):
            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 "Invalid API key" 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."""
        # Override GJALLA_DIR to use temp home
        monkeypatch.setattr(init_module, "GJALLA_DIR", home_dir / ".gjalla")
        monkeypatch.setattr(init_module, "CONFIG_PATH", home_dir / ".gjalla" / "config.yaml")

        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")

        monkeypatch.setattr(init_module, "CONFIG_PATH", config_path)

        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"

        monkeypatch.setattr(init_module, "GJALLA_DIR", gjalla_dir)
        monkeypatch.setattr(init_module, "CONFIG_PATH", config_path)

        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"

        monkeypatch.setattr(init_module, "GJALLA_DIR", gjalla_dir)
        monkeypatch.setattr(init_module, "CONFIG_PATH", config_path)

        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

            result = init_module.verify_api_key("gj_valid", "https://api.gjalla.io")
            assert result is True

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

            result = init_module.verify_api_key("gj_invalid", "https://api.gjalla.io")
            assert result is False

    def test_verify_api_key_network_error(self):
        """Should return False on network error."""
        import httpx
        with patch("httpx.get", side_effect=httpx.RequestError("Network error")):
            result = init_module.verify_api_key("gj_test", "https://api.gjalla.io")
            assert result is False


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 = init_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 = init_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 = init_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 = init_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(init_module, "list_projects") as mock_list:
            mock_list.return_value = [
                {"id": 1, "title": "my-repo", "description": ""},
            ]

            result = init_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(init_module, "list_projects") as mock_list:
            mock_list.return_value = [
                {"id": 2, "title": "another-repo", "description": ""},
            ]

            result = init_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(init_module, "list_projects") as mock_list:
            mock_list.return_value = [
                {"id": 3, "title": "prefix-my-repo-suffix", "description": ""},
            ]

            result = init_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(init_module, "list_projects") as mock_list:
            mock_list.return_value = [
                {"id": 1, "title": "unrelated", "description": ""},
            ]

            result = init_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(init_module, "list_projects") as mock_list:
            result = init_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 = init_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(init_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 = init_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(init_module, "list_projects") as mock_list:
            mock_list.return_value = []
            result = init_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(init_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 = init_module.select_project_interactively("gj_test", "https://api.gjalla.io")
                assert result is None


class TestHTTPClientNewFunctions:
    """Tests for new HTTP client functions (sync wrappers)."""

    def test_create_project_success(self):
        """Should create project and return response."""
        from gjalla_precommit.protocol import http_client
        from unittest.mock import AsyncMock

        with patch.object(http_client.httpx, "AsyncClient") as mock_client_class:
            mock_response = MagicMock()
            mock_response.status_code = 201
            mock_response.json.return_value = {"id": 123, "title": "New Project"}

            # Create properly configured async mock
            mock_client = AsyncMock()
            mock_client.post.return_value = mock_response

            mock_client_class.return_value.__aenter__.return_value = mock_client
            mock_client_class.return_value.__aexit__.return_value = None

            result = http_client.create_project(
                "New Project", "gj_test", "https://api.gjalla.io", "Description"
            )
            assert result["id"] == 123
            assert result["title"] == "New Project"

    def test_create_project_forbidden(self):
        """Should raise RuntimeError for 403 response."""
        from gjalla_precommit.protocol import http_client
        from unittest.mock import AsyncMock

        with patch.object(http_client.httpx, "AsyncClient") as mock_client_class:
            mock_response = MagicMock()
            mock_response.status_code = 403

            mock_client = AsyncMock()
            mock_client.post.return_value = mock_response

            mock_client_class.return_value.__aenter__.return_value = mock_client
            mock_client_class.return_value.__aexit__.return_value = None

            with pytest.raises(RuntimeError, match="permission"):
                http_client.create_project(
                    "New Project", "gj_test", "https://api.gjalla.io", ""
                )

    def test_batch_create_rules_success(self):
        """Should batch create rules and return response."""
        from gjalla_precommit.protocol import http_client
        from unittest.mock import AsyncMock

        with patch.object(http_client.httpx, "AsyncClient") as mock_client_class:
            mock_response = MagicMock()
            mock_response.status_code = 201
            mock_response.json.return_value = {"created": 2}

            mock_client = AsyncMock()
            mock_client.post.return_value = mock_response

            mock_client_class.return_value.__aenter__.return_value = mock_client
            mock_client_class.return_value.__aexit__.return_value = None

            rules = [
                {"ruleType": "invariant", "name": "rule1", "description": ""},
                {"ruleType": "invariant", "name": "rule2", "description": ""},
            ]

            result = http_client.batch_create_rules(
                123, rules, "gj_test", "https://api.gjalla.io"
            )
            assert result["created"] == 2

    def test_batch_create_rules_project_not_found(self):
        """Should raise RuntimeError for 404 response."""
        from gjalla_precommit.protocol import http_client
        from unittest.mock import AsyncMock

        with patch.object(http_client.httpx, "AsyncClient") as mock_client_class:
            mock_response = MagicMock()
            mock_response.status_code = 404

            mock_client = AsyncMock()
            mock_client.post.return_value = mock_response

            mock_client_class.return_value.__aenter__.return_value = mock_client
            mock_client_class.return_value.__aexit__.return_value = None

            with pytest.raises(RuntimeError, match="not found"):
                http_client.batch_create_rules(
                    999, [], "gj_test", "https://api.gjalla.io"
                )


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.json"
        assert gjalla_config.exists()
        data = json.loads(gjalla_config.read_text())
        assert data["project_id"] == 42
        assert data["api_url"] == "https://gjalla.io"
