"""
Unit tests for config.py.

All tests verify fail-fast behaviour — every bad input must raise
immediately with a meaningful error message.
"""

from __future__ import annotations

import json
import os
from pathlib import Path

import pytest

from active_claude_github_mcp.config import ConfigError, load_config, require_env


class TestLoadConfig:
    def test_loads_valid_config(self, project_dir):
        config = load_config(project_dir)
        assert config.owner == "telejester"
        assert config.account_type == "personal"
        assert config.repos.private == "telejester/claude-skills-dev"

    def test_raises_if_project_dir_missing(self, tmp_path):
        missing = tmp_path / "does-not-exist"
        with pytest.raises(ConfigError, match="does not exist"):
            load_config(missing)

    def test_raises_if_config_file_missing(self, empty_project_dir):
        with pytest.raises(ConfigError, match="not found"):
            load_config(empty_project_dir)

    def test_raises_on_invalid_json(self, tmp_path):
        claude_dir = tmp_path / ".claude"
        claude_dir.mkdir()
        (claude_dir / "github-config.json").write_text("not json {{", encoding="utf-8")
        with pytest.raises(ConfigError, match="not valid JSON"):
            load_config(tmp_path)

    def test_raises_on_missing_required_field(self, tmp_path, valid_config_dict):
        del valid_config_dict["owner"]
        claude_dir = tmp_path / ".claude"
        claude_dir.mkdir()
        (claude_dir / "github-config.json").write_text(
            json.dumps(valid_config_dict), encoding="utf-8"
        )
        with pytest.raises(ConfigError, match="failed validation"):
            load_config(tmp_path)

    def test_raises_on_wrong_skill_name(self, tmp_path, valid_config_dict):
        valid_config_dict["skill"] = "some-other-skill"
        claude_dir = tmp_path / ".claude"
        claude_dir.mkdir()
        (claude_dir / "github-config.json").write_text(
            json.dumps(valid_config_dict), encoding="utf-8"
        )
        with pytest.raises(ConfigError, match="failed validation"):
            load_config(tmp_path)

    def test_raises_on_short_node_id(self, tmp_path, valid_config_dict):
        valid_config_dict["project_discussion_id"] = "short"
        claude_dir = tmp_path / ".claude"
        claude_dir.mkdir()
        (claude_dir / "github-config.json").write_text(
            json.dumps(valid_config_dict), encoding="utf-8"
        )
        with pytest.raises(ConfigError, match="suspiciously short"):
            load_config(tmp_path)

    def test_raises_on_empty_category_id(self, tmp_path, valid_config_dict):
        valid_config_dict["discussion_categories"]["Architecture"]["id"] = ""
        claude_dir = tmp_path / ".claude"
        claude_dir.mkdir()
        (claude_dir / "github-config.json").write_text(
            json.dumps(valid_config_dict), encoding="utf-8"
        )
        with pytest.raises(ConfigError, match="failed validation"):
            load_config(tmp_path)

    def test_config_is_immutable(self, project_dir):
        config = load_config(project_dir)
        with pytest.raises(Exception):
            config.owner = "other"  # type: ignore[misc]

    def test_loads_related_repos_field(self, tmp_path, valid_config_dict):
        # #98 — related_repos is optional and parses alongside the
        # rest of the schema when present.
        valid_config_dict["related_repos"] = [
            {
                "repo": "other-org/shared",
                "relationship": "sibling",
                "description": "peer project",
            }
        ]
        claude_dir = tmp_path / ".claude"
        claude_dir.mkdir()
        (claude_dir / "github-config.json").write_text(
            json.dumps(valid_config_dict), encoding="utf-8"
        )
        config = load_config(tmp_path)
        assert len(config.related_repos) == 1
        assert config.related_repos[0].relationship == "sibling"

    def test_contribution_target_emits_deprecation_warning(
        self, tmp_path, valid_config_dict, recwarn
    ):
        # #98 — the pre-1.3.0 umbrella `contribution_target` still
        # parses but must emit a DeprecationWarning so the user gets
        # prompted to migrate to a precise sub-type.
        valid_config_dict["related_repos"] = [
            {"repo": "legacy-org/tool", "relationship": "contribution_target"},
            {"repo": "another-org/x", "relationship": "contribution_target"},
            {"repo": "new-org/y", "relationship": "sibling"},  # control
        ]
        claude_dir = tmp_path / ".claude"
        claude_dir.mkdir()
        (claude_dir / "github-config.json").write_text(
            json.dumps(valid_config_dict), encoding="utf-8"
        )
        config = load_config(tmp_path)

        # Config still loads, contribution_target entries preserved.
        types = [r.relationship for r in config.related_repos]
        assert types.count("contribution_target") == 2

        dep_warnings = [
            w for w in recwarn.list
            if issubclass(w.category, DeprecationWarning)
        ]
        assert len(dep_warnings) == 1, (
            "Exactly one DeprecationWarning for contribution_target usage"
        )
        msg = str(dep_warnings[0].message)
        assert "contribution_target" in msg
        assert "legacy-org/tool" in msg
        assert "another-org/x" in msg
        # The non-deprecated sibling entry is not named in the warning.
        assert "new-org/y" not in msg

    def test_no_deprecation_warning_for_clean_config(
        self, project_dir, recwarn
    ):
        load_config(project_dir)
        dep_warnings = [
            w for w in recwarn.list
            if issubclass(w.category, DeprecationWarning)
        ]
        assert dep_warnings == [], (
            "A clean config (no contribution_target) must emit zero "
            "DeprecationWarnings on load."
        )


class TestRequireEnv:
    def test_returns_value_when_set(self, monkeypatch):
        monkeypatch.setenv("TEST_VAR_123", "myvalue")
        assert require_env("TEST_VAR_123") == "myvalue"

    def test_raises_when_not_set(self, monkeypatch):
        monkeypatch.delenv("TEST_VAR_123", raising=False)
        with pytest.raises(ConfigError, match="TEST_VAR_123"):
            require_env("TEST_VAR_123")

    def test_raises_when_empty(self, monkeypatch):
        monkeypatch.setenv("TEST_VAR_123", "   ")
        with pytest.raises(ConfigError, match="TEST_VAR_123"):
            require_env("TEST_VAR_123")
if __name__ == "__main__":
    import sys
    import pytest
    sys.exit(pytest.main([__file__, "-v"]))
