#!/usr/bin/env python3
"""
Tests for SourceManager class (config source registry management)
"""

import json
from pathlib import Path

import pytest

from skill_seekers.services.source_manager import SourceManager


@pytest.fixture
def temp_config_dir(tmp_path):
    """Create temporary config directory for tests."""
    config_dir = tmp_path / "test_config"
    config_dir.mkdir()
    return config_dir


@pytest.fixture
def source_manager(temp_config_dir):
    """Create SourceManager instance with temp config."""
    return SourceManager(config_dir=str(temp_config_dir))


class TestSourceManagerInit:
    """Test SourceManager initialization."""

    def test_init_creates_config_dir(self, tmp_path):
        """Test that initialization creates config directory."""
        config_dir = tmp_path / "new_config"
        manager = SourceManager(config_dir=str(config_dir))

        assert config_dir.exists()
        assert manager.config_dir == config_dir

    def test_init_creates_registry_file(self, temp_config_dir):
        """Test that initialization creates registry file."""
        _manager = SourceManager(config_dir=str(temp_config_dir))
        registry_file = temp_config_dir / "sources.json"

        assert registry_file.exists()

        # Verify initial structure
        with open(registry_file) as f:
            data = json.load(f)
            assert data == {"version": "1.0", "sources": []}

    def test_init_preserves_existing_registry(self, temp_config_dir):
        """Test that initialization doesn't overwrite existing registry."""
        registry_file = temp_config_dir / "sources.json"

        # Create existing registry
        existing_data = {
            "version": "1.0",
            "sources": [{"name": "test", "git_url": "https://example.com/repo.git"}],
        }
        with open(registry_file, "w") as f:
            json.dump(existing_data, f)

        # Initialize manager
        _manager = SourceManager(config_dir=str(temp_config_dir))

        # Verify data preserved
        with open(registry_file) as f:
            data = json.load(f)
            assert len(data["sources"]) == 1

    def test_init_with_default_config_dir(self):
        """Test initialization with default config directory."""
        manager = SourceManager()

        expected = Path.home() / ".skill-seekers"
        assert manager.config_dir == expected


class TestAddSource:
    """Test adding config sources."""

    def test_add_source_minimal(self, source_manager):
        """Test adding source with minimal parameters."""
        source = source_manager.add_source(
            name="team", git_url="https://github.com/myorg/configs.git"
        )

        assert source["name"] == "team"
        assert source["git_url"] == "https://github.com/myorg/configs.git"
        assert source["type"] == "github"
        assert source["token_env"] == "GITHUB_TOKEN"
        assert source["branch"] == "main"
        assert source["enabled"] is True
        assert source["priority"] == 100
        assert "added_at" in source
        assert "updated_at" in source

    def test_add_source_full_parameters(self, source_manager):
        """Test adding source with all parameters."""
        source = source_manager.add_source(
            name="company",
            git_url="https://gitlab.company.com/platform/configs.git",
            source_type="gitlab",
            token_env="CUSTOM_TOKEN",
            branch="develop",
            priority=1,
            enabled=False,
        )

        assert source["name"] == "company"
        assert source["type"] == "gitlab"
        assert source["token_env"] == "CUSTOM_TOKEN"
        assert source["branch"] == "develop"
        assert source["priority"] == 1
        assert source["enabled"] is False

    def test_add_source_normalizes_name(self, source_manager):
        """Test that source names are normalized to lowercase."""
        source = source_manager.add_source(name="MyTeam", git_url="https://github.com/org/repo.git")

        assert source["name"] == "myteam"

    def test_add_source_invalid_name_empty(self, source_manager):
        """Test that empty source names are rejected."""
        with pytest.raises(ValueError, match="Invalid source name"):
            source_manager.add_source(name="", git_url="https://github.com/org/repo.git")

    def test_add_source_invalid_name_special_chars(self, source_manager):
        """Test that source names with special characters are rejected."""
        with pytest.raises(ValueError, match="Invalid source name"):
            source_manager.add_source(
                name="team@company", git_url="https://github.com/org/repo.git"
            )

    def test_add_source_valid_name_with_hyphens(self, source_manager):
        """Test that source names with hyphens are allowed."""
        source = source_manager.add_source(
            name="team-alpha", git_url="https://github.com/org/repo.git"
        )

        assert source["name"] == "team-alpha"

    def test_add_source_valid_name_with_underscores(self, source_manager):
        """Test that source names with underscores are allowed."""
        source = source_manager.add_source(
            name="team_alpha", git_url="https://github.com/org/repo.git"
        )

        assert source["name"] == "team_alpha"

    def test_add_source_empty_git_url(self, source_manager):
        """Test that empty git URLs are rejected."""
        with pytest.raises(ValueError, match="git_url cannot be empty"):
            source_manager.add_source(name="team", git_url="")

    def test_add_source_strips_git_url(self, source_manager):
        """Test that git URLs are stripped of whitespace."""
        source = source_manager.add_source(
            name="team", git_url="  https://github.com/org/repo.git  "
        )

        assert source["git_url"] == "https://github.com/org/repo.git"

    def test_add_source_updates_existing(self, source_manager):
        """Test that adding existing source updates it."""
        # Add initial source
        source1 = source_manager.add_source(name="team", git_url="https://github.com/org/repo1.git")

        # Update source
        source2 = source_manager.add_source(name="team", git_url="https://github.com/org/repo2.git")

        # Verify updated
        assert source2["git_url"] == "https://github.com/org/repo2.git"
        assert source2["added_at"] == source1["added_at"]  # Preserved
        assert source2["updated_at"] > source1["added_at"]  # Updated

        # Verify only one source exists
        sources = source_manager.list_sources()
        assert len(sources) == 1

    def test_add_source_persists_to_file(self, source_manager, temp_config_dir):
        """Test that added sources are persisted to file."""
        source_manager.add_source(name="team", git_url="https://github.com/org/repo.git")

        # Read file directly
        registry_file = temp_config_dir / "sources.json"
        with open(registry_file) as f:
            data = json.load(f)

        assert len(data["sources"]) == 1
        assert data["sources"][0]["name"] == "team"

    def test_add_multiple_sources_sorted_by_priority(self, source_manager):
        """Test that multiple sources are sorted by priority."""
        source_manager.add_source(name="low", git_url="https://example.com/1.git", priority=100)
        source_manager.add_source(name="high", git_url="https://example.com/2.git", priority=1)
        source_manager.add_source(name="medium", git_url="https://example.com/3.git", priority=50)

        sources = source_manager.list_sources()

        assert [s["name"] for s in sources] == ["high", "medium", "low"]
        assert [s["priority"] for s in sources] == [1, 50, 100]


class TestGetSource:
    """Test retrieving config sources."""

    def test_get_source_exact_match(self, source_manager):
        """Test getting source with exact name match."""
        source_manager.add_source(name="team", git_url="https://github.com/org/repo.git")

        source = source_manager.get_source("team")

        assert source["name"] == "team"

    def test_get_source_case_insensitive(self, source_manager):
        """Test getting source is case-insensitive."""
        source_manager.add_source(name="MyTeam", git_url="https://github.com/org/repo.git")

        source = source_manager.get_source("myteam")

        assert source["name"] == "myteam"

    def test_get_source_not_found(self, source_manager):
        """Test error when source not found."""
        with pytest.raises(KeyError, match="Source 'nonexistent' not found"):
            source_manager.get_source("nonexistent")

    def test_get_source_not_found_shows_available(self, source_manager):
        """Test error message shows available sources."""
        source_manager.add_source(name="team1", git_url="https://example.com/1.git")
        source_manager.add_source(name="team2", git_url="https://example.com/2.git")

        with pytest.raises(KeyError, match="Available sources: team1, team2"):
            source_manager.get_source("team3")

    def test_get_source_empty_registry(self, source_manager):
        """Test error when registry is empty."""
        with pytest.raises(KeyError, match="Available sources: none"):
            source_manager.get_source("team")


class TestListSources:
    """Test listing config sources."""

    def test_list_sources_empty(self, source_manager):
        """Test listing sources when registry is empty."""
        sources = source_manager.list_sources()

        assert sources == []

    def test_list_sources_multiple(self, source_manager):
        """Test listing multiple sources."""
        source_manager.add_source(name="team1", git_url="https://example.com/1.git")
        source_manager.add_source(name="team2", git_url="https://example.com/2.git")
        source_manager.add_source(name="team3", git_url="https://example.com/3.git")

        sources = source_manager.list_sources()

        assert len(sources) == 3

    def test_list_sources_sorted_by_priority(self, source_manager):
        """Test that sources are sorted by priority."""
        source_manager.add_source(name="low", git_url="https://example.com/1.git", priority=100)
        source_manager.add_source(name="high", git_url="https://example.com/2.git", priority=1)

        sources = source_manager.list_sources()

        assert sources[0]["name"] == "high"
        assert sources[1]["name"] == "low"

    def test_list_sources_enabled_only(self, source_manager):
        """Test listing only enabled sources."""
        source_manager.add_source(
            name="enabled1", git_url="https://example.com/1.git", enabled=True
        )
        source_manager.add_source(
            name="disabled", git_url="https://example.com/2.git", enabled=False
        )
        source_manager.add_source(
            name="enabled2", git_url="https://example.com/3.git", enabled=True
        )

        sources = source_manager.list_sources(enabled_only=True)

        assert len(sources) == 2
        assert all(s["enabled"] for s in sources)
        assert sorted([s["name"] for s in sources]) == ["enabled1", "enabled2"]

    def test_list_sources_all_when_some_disabled(self, source_manager):
        """Test listing all sources includes disabled ones."""
        source_manager.add_source(name="enabled", git_url="https://example.com/1.git", enabled=True)
        source_manager.add_source(
            name="disabled", git_url="https://example.com/2.git", enabled=False
        )

        sources = source_manager.list_sources(enabled_only=False)

        assert len(sources) == 2


class TestRemoveSource:
    """Test removing config sources."""

    def test_remove_source_exists(self, source_manager):
        """Test removing existing source."""
        source_manager.add_source(name="team", git_url="https://github.com/org/repo.git")

        result = source_manager.remove_source("team")

        assert result is True
        assert len(source_manager.list_sources()) == 0

    def test_remove_source_case_insensitive(self, source_manager):
        """Test removing source is case-insensitive."""
        source_manager.add_source(name="MyTeam", git_url="https://github.com/org/repo.git")

        result = source_manager.remove_source("myteam")

        assert result is True

    def test_remove_source_not_found(self, source_manager):
        """Test removing non-existent source returns False."""
        result = source_manager.remove_source("nonexistent")

        assert result is False

    def test_remove_source_persists_to_file(self, source_manager, temp_config_dir):
        """Test that source removal is persisted to file."""
        source_manager.add_source(name="team1", git_url="https://example.com/1.git")
        source_manager.add_source(name="team2", git_url="https://example.com/2.git")

        source_manager.remove_source("team1")

        # Read file directly
        registry_file = temp_config_dir / "sources.json"
        with open(registry_file) as f:
            data = json.load(f)

        assert len(data["sources"]) == 1
        assert data["sources"][0]["name"] == "team2"

    def test_remove_source_from_multiple(self, source_manager):
        """Test removing one source from multiple."""
        source_manager.add_source(name="team1", git_url="https://example.com/1.git")
        source_manager.add_source(name="team2", git_url="https://example.com/2.git")
        source_manager.add_source(name="team3", git_url="https://example.com/3.git")

        source_manager.remove_source("team2")

        sources = source_manager.list_sources()
        assert len(sources) == 2
        assert sorted([s["name"] for s in sources]) == ["team1", "team3"]


class TestUpdateSource:
    """Test updating config sources."""

    def test_update_source_git_url(self, source_manager):
        """Test updating source git URL."""
        source_manager.add_source(name="team", git_url="https://github.com/org/repo1.git")

        updated = source_manager.update_source(
            name="team", git_url="https://github.com/org/repo2.git"
        )

        assert updated["git_url"] == "https://github.com/org/repo2.git"

    def test_update_source_branch(self, source_manager):
        """Test updating source branch."""
        source_manager.add_source(name="team", git_url="https://github.com/org/repo.git")

        updated = source_manager.update_source(name="team", branch="develop")

        assert updated["branch"] == "develop"

    def test_update_source_enabled(self, source_manager):
        """Test updating source enabled status."""
        source_manager.add_source(
            name="team", git_url="https://github.com/org/repo.git", enabled=True
        )

        updated = source_manager.update_source(name="team", enabled=False)

        assert updated["enabled"] is False

    def test_update_source_priority(self, source_manager):
        """Test updating source priority."""
        source_manager.add_source(
            name="team", git_url="https://github.com/org/repo.git", priority=100
        )

        updated = source_manager.update_source(name="team", priority=1)

        assert updated["priority"] == 1

    def test_update_source_multiple_fields(self, source_manager):
        """Test updating multiple fields at once."""
        source_manager.add_source(name="team", git_url="https://github.com/org/repo.git")

        updated = source_manager.update_source(
            name="team",
            git_url="https://gitlab.com/org/repo.git",
            type="gitlab",
            branch="develop",
            priority=1,
        )

        assert updated["git_url"] == "https://gitlab.com/org/repo.git"
        assert updated["type"] == "gitlab"
        assert updated["branch"] == "develop"
        assert updated["priority"] == 1

    def test_update_source_updates_timestamp(self, source_manager):
        """Test that update modifies updated_at timestamp."""
        source = source_manager.add_source(name="team", git_url="https://github.com/org/repo.git")
        original_updated = source["updated_at"]

        updated = source_manager.update_source(name="team", branch="develop")

        assert updated["updated_at"] > original_updated

    def test_update_source_not_found(self, source_manager):
        """Test error when updating non-existent source."""
        with pytest.raises(KeyError, match="Source 'nonexistent' not found"):
            source_manager.update_source(name="nonexistent", branch="main")

    def test_update_source_resorts_by_priority(self, source_manager):
        """Test that updating priority re-sorts sources."""
        source_manager.add_source(name="team1", git_url="https://example.com/1.git", priority=1)
        source_manager.add_source(name="team2", git_url="https://example.com/2.git", priority=2)

        # Change team2 to higher priority
        source_manager.update_source(name="team2", priority=0)

        sources = source_manager.list_sources()
        assert sources[0]["name"] == "team2"
        assert sources[1]["name"] == "team1"


class TestDefaultTokenEnv:
    """Test default token environment variable detection."""

    def test_default_token_env_github(self, source_manager):
        """Test GitHub sources get GITHUB_TOKEN."""
        source = source_manager.add_source(
            name="team", git_url="https://github.com/org/repo.git", source_type="github"
        )

        assert source["token_env"] == "GITHUB_TOKEN"

    def test_default_token_env_gitlab(self, source_manager):
        """Test GitLab sources get GITLAB_TOKEN."""
        source = source_manager.add_source(
            name="team", git_url="https://gitlab.com/org/repo.git", source_type="gitlab"
        )

        assert source["token_env"] == "GITLAB_TOKEN"

    def test_default_token_env_gitea(self, source_manager):
        """Test Gitea sources get GITEA_TOKEN."""
        source = source_manager.add_source(
            name="team", git_url="https://gitea.example.com/org/repo.git", source_type="gitea"
        )

        assert source["token_env"] == "GITEA_TOKEN"

    def test_default_token_env_bitbucket(self, source_manager):
        """Test Bitbucket sources get BITBUCKET_TOKEN."""
        source = source_manager.add_source(
            name="team", git_url="https://bitbucket.org/org/repo.git", source_type="bitbucket"
        )

        assert source["token_env"] == "BITBUCKET_TOKEN"

    def test_default_token_env_custom(self, source_manager):
        """Test custom sources get GIT_TOKEN."""
        source = source_manager.add_source(
            name="team", git_url="https://git.example.com/org/repo.git", source_type="custom"
        )

        assert source["token_env"] == "GIT_TOKEN"

    def test_override_token_env(self, source_manager):
        """Test that custom token_env overrides default."""
        source = source_manager.add_source(
            name="team",
            git_url="https://github.com/org/repo.git",
            source_type="github",
            token_env="MY_CUSTOM_TOKEN",
        )

        assert source["token_env"] == "MY_CUSTOM_TOKEN"


class TestRegistryPersistence:
    """Test registry file I/O."""

    def test_registry_atomic_write(self, source_manager, temp_config_dir):
        """Test that registry writes are atomic (temp file + rename)."""
        source_manager.add_source(name="team", git_url="https://github.com/org/repo.git")

        # Verify no .tmp file left behind
        temp_files = list(temp_config_dir.glob("*.tmp"))
        assert len(temp_files) == 0

    def test_registry_json_formatting(self, source_manager, temp_config_dir):
        """Test that registry JSON is properly formatted."""
        source_manager.add_source(name="team", git_url="https://github.com/org/repo.git")

        registry_file = temp_config_dir / "sources.json"
        content = registry_file.read_text()

        # Verify it's pretty-printed
        assert "  " in content  # Indentation
        data = json.loads(content)
        assert "version" in data
        assert "sources" in data

    def test_registry_corrupted_file(self, temp_config_dir):
        """Test error handling for corrupted registry file."""
        registry_file = temp_config_dir / "sources.json"
        registry_file.write_text("{ invalid json }")

        # The constructor will fail when trying to read the corrupted file
        # during initialization, but it actually creates a new valid registry
        # So we need to test reading a corrupted file after construction
        manager = SourceManager(config_dir=str(temp_config_dir))

        # Corrupt the file after initialization
        registry_file.write_text("{ invalid json }")

        # Now _read_registry should fail
        with pytest.raises(ValueError, match="Corrupted registry file"):
            manager._read_registry()
