"""Tests for mcp-config-sync package."""

import json
import os
import subprocess
import sys
from pathlib import Path

import pytest

# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent.parent))

from mcp_config_sync import __version__
from mcp_config_sync.config import (
    read_config,
    write_config,
    merge_configs,
    resolve_env_vars,
    create_template,
    list_servers,
)
from mcp_config_sync.validator import validate_config
from mcp_config_sync.diff import diff_configs


# ============================================================================
# VERSION
# ============================================================================

class TestVersion:
    def test_version_exists(self):
        assert __version__ == "0.1.0"


# ============================================================================
# CONFIG READ/WRITE
# ============================================================================

class TestConfig:
    def test_write_and_read(self, tmp_path):
        config = {"mcpServers": {"test": {"command": "echo"}}}
        path = tmp_path / "test.json"
        write_config(path, config)
        result = read_config(path)
        assert result == config

    def test_read_nonexistent(self):
        with pytest.raises(FileNotFoundError):
            read_config("/nonexistent/path.json")

    def test_write_creates_dirs(self, tmp_path):
        path = tmp_path / "nested" / "deep" / "config.json"
        write_config(path, {"mcpServers": {}})
        assert path.exists()

    def test_create_template(self):
        template = create_template()
        assert "mcpServers" in template
        assert "example-server" in template["mcpServers"]
        assert "command" in template["mcpServers"]["example-server"]

    def test_list_servers_stdio(self):
        config = {
            "mcpServers": {
                "my-server": {"command": "node", "args": ["server.js"]},
            }
        }
        servers = list_servers(config)
        assert len(servers) == 1
        assert servers[0]["name"] == "my-server"
        assert servers[0]["transport"] == "stdio"
        assert servers[0]["command"] == "node"

    def test_list_servers_http(self):
        config = {
            "mcpServers": {
                "remote": {"url": "http://localhost:8080/mcp"},
            }
        }
        servers = list_servers(config)
        assert len(servers) == 1
        assert servers[0]["transport"] == "http"

    def test_list_servers_empty(self):
        assert list_servers({"mcpServers": {}}) == []
        assert list_servers({}) == []


# ============================================================================
# MERGE
# ============================================================================

class TestMerge:
    def test_merge_union(self):
        base = {"mcpServers": {"a": {"command": "a"}}}
        overlay = {"mcpServers": {"b": {"command": "b"}}}
        result = merge_configs(base, overlay)
        assert "a" in result["mcpServers"]
        assert "b" in result["mcpServers"]

    def test_merge_overlay_wins(self):
        base = {"mcpServers": {"s": {"command": "old"}}}
        overlay = {"mcpServers": {"s": {"command": "new"}}}
        result = merge_configs(base, overlay)
        assert result["mcpServers"]["s"]["command"] == "new"

    def test_merge_preserves_base_only(self):
        base = {"mcpServers": {"only-in-base": {"command": "base"}}}
        overlay = {"mcpServers": {"only-in-overlay": {"command": "overlay"}}}
        result = merge_configs(base, overlay)
        assert "only-in-base" in result["mcpServers"]
        assert "only-in-overlay" in result["mcpServers"]

    def test_merge_deep_copy(self):
        """Merge should not mutate originals."""
        base = {"mcpServers": {"s": {"command": "base", "env": {"A": "1"}}}}
        overlay = {"mcpServers": {"s": {"command": "overlay"}}}
        result = merge_configs(base, overlay)
        result["mcpServers"]["s"]["command"] = "mutated"
        assert base["mcpServers"]["s"]["command"] == "base"
        assert overlay["mcpServers"]["s"]["command"] == "overlay"


# ============================================================================
# ENV VAR RESOLUTION
# ============================================================================

class TestEnvVarResolution:
    def test_resolve_env_var(self, monkeypatch):
        monkeypatch.setenv("TEST_VAR", "hello")
        config = {"mcpServers": {"s": {"command": "${TEST_VAR}"}}}
        result = resolve_env_vars(config)
        assert result["mcpServers"]["s"]["command"] == "hello"

    def test_unresolved_env_var_preserved(self):
        config = {"mcpServers": {"s": {"command": "${NONEXISTENT_VAR_12345}"}}}
        result = resolve_env_vars(config)
        assert result["mcpServers"]["s"]["command"] == "${NONEXISTENT_VAR_12345}"

    def test_resolve_in_args(self, monkeypatch):
        monkeypatch.setenv("MY_PATH", "/usr/bin")
        config = {"mcpServers": {"s": {"command": "node", "args": ["${MY_PATH}/server.js"]}}}
        result = resolve_env_vars(config)
        assert result["mcpServers"]["s"]["args"][0] == "/usr/bin/server.js"

    def test_resolve_in_env(self, monkeypatch):
        monkeypatch.setenv("SECRET", "s3cr3t")
        config = {"mcpServers": {"s": {"command": "x", "env": {"API_KEY": "${SECRET}"}}}}
        result = resolve_env_vars(config)
        assert result["mcpServers"]["s"]["env"]["API_KEY"] == "s3cr3t"


# ============================================================================
# VALIDATOR
# ============================================================================

class TestValidator:
    def test_valid_config(self):
        config = {"mcpServers": {"s": {"command": "node", "args": ["server.js"]}}}
        errors = validate_config(config)
        assert errors == []

    def test_missing_mcp_servers(self):
        errors = validate_config({})
        assert any("mcpServers" in e for e in errors)

    def test_not_a_dict(self):
        errors = validate_config("not a dict")
        assert len(errors) == 1

    def test_server_no_command_or_url(self):
        config = {"mcpServers": {"bad": {"args": ["--foo"]}}}
        errors = validate_config(config)
        assert any("command" in e or "url" in e for e in errors)

    def test_server_with_url(self):
        config = {"mcpServers": {"remote": {"url": "http://localhost:8080"}}}
        errors = validate_config(config)
        assert errors == []

    def test_invalid_args_type(self):
        config = {"mcpServers": {"s": {"command": "node", "args": "not-a-list"}}}
        errors = validate_config(config)
        assert any("array" in e for e in errors)

    def test_invalid_env_type(self):
        config = {"mcpServers": {"s": {"command": "node", "env": ["not", "dict"]}}}
        errors = validate_config(config)
        assert any("object" in e for e in errors)

    def test_invalid_transport_type(self):
        config = {"mcpServers": {"s": {"command": "node", "type": "websocket"}}}
        errors = validate_config(config)
        assert any("type" in e for e in errors)

    def test_valid_transport_types(self):
        for t in ["stdio", "http", "sse"]:
            config = {"mcpServers": {"s": {"command": "node", "type": t}}}
            errors = validate_config(config)
            type_errors = [e for e in errors if "type" in e.lower()]
            assert type_errors == [], f"Transport '{t}' should be valid"

    def test_unknown_top_level_key(self):
        config = {"mcpServers": {"s": {"command": "x"}}, "extraKey": True}
        errors = validate_config(config)
        assert any("extraKey" in e for e in errors)

    def test_unknown_server_key(self):
        config = {"mcpServers": {"s": {"command": "x", "unknownField": True}}}
        errors = validate_config(config)
        assert any("unknownField" in e for e in errors)


# ============================================================================
# DIFF
# ============================================================================

class TestDiff:
    def test_diff_identical(self):
        config = {"mcpServers": {"s": {"command": "node"}}}
        lines = diff_configs(config, config)
        # Identical configs should produce minimal/no diff
        assert isinstance(lines, list)

    def test_diff_added_server(self):
        a = {"mcpServers": {"s1": {"command": "node"}}}
        b = {"mcpServers": {"s1": {"command": "node"}, "s2": {"command": "python"}}}
        lines = diff_configs(a, b, label_a="A", label_b="B")
        output = "\n".join(lines)
        assert "s2" in output

    def test_diff_removed_server(self):
        a = {"mcpServers": {"s1": {"command": "node"}, "s2": {"command": "python"}}}
        b = {"mcpServers": {"s1": {"command": "node"}}}
        lines = diff_configs(a, b, label_a="A", label_b="B")
        output = "\n".join(lines)
        assert "s2" in output

    def test_diff_modified_server(self):
        a = {"mcpServers": {"s": {"command": "node"}}}
        b = {"mcpServers": {"s": {"command": "python"}}}
        lines = diff_configs(a, b, label_a="A", label_b="B")
        output = "\n".join(lines)
        assert "s" in output


# ============================================================================
# CLI SMOKE TESTS
# ============================================================================

class TestCLI:
    def test_help(self):
        result = subprocess.run(
            [sys.executable, "-m", "mcp_config_sync", "--help"],
            capture_output=True, text=True, cwd=str(Path(__file__).parent.parent.parent)
        )
        assert result.returncode == 0
        assert "detect" in result.stdout
        assert "validate" in result.stdout
        assert "sync" in result.stdout

    def test_detect_runs(self):
        result = subprocess.run(
            [sys.executable, "-m", "mcp_config_sync", "detect"],
            capture_output=True, text=True, cwd=str(Path(__file__).parent.parent.parent)
        )
        # Should not crash, exit code 0
        assert result.returncode == 0

    def test_validate_template(self, tmp_path):
        """Create a valid config and validate it."""
        config = create_template()
        path = tmp_path / ".mcp.json"
        write_config(path, config)
        result = subprocess.run(
            [sys.executable, "-m", "mcp_config_sync", "validate", str(path)],
            capture_output=True, text=True, cwd=str(Path(__file__).parent.parent.parent)
        )
        assert result.returncode == 0
        assert "Valid" in result.stdout or "valid" in result.stdout.lower()

    def test_init(self, tmp_path):
        target = tmp_path / ".mcp.json"
        result = subprocess.run(
            [sys.executable, "-m", "mcp_config_sync", "init", str(target)],
            capture_output=True, text=True, cwd=str(Path(__file__).parent.parent.parent)
        )
        assert result.returncode == 0
        assert target.exists()
        data = json.loads(target.read_text(encoding="utf-8"))
        assert "mcpServers" in data
