"""Tests for domain value objects."""

import uuid

import pytest

from mcp_hangar.domain.value_objects import (
    CommandLine,
    CorrelationId,
    DockerImage,
    Endpoint,
    EnvironmentVariables,
    HealthCheckInterval,
    IdleTTL,
    MaxConsecutiveFailures,
    ProviderId,
    ProviderMode,
    TimeoutSeconds,
    ToolName,
)

# ProviderId Tests


def test_provider_id_valid():
    """Test valid provider IDs."""
    pid = ProviderId("math-provider")
    assert pid.value == "math-provider"
    assert str(pid) == "math-provider"


def test_provider_id_alphanumeric_underscore():
    """Test provider ID with alphanumeric and underscore."""
    pid = ProviderId("math_provider_123")
    assert pid.value == "math_provider_123"


def test_provider_id_empty():
    """Test that empty provider ID raises ValueError."""
    with pytest.raises(ValueError, match="cannot be empty"):
        ProviderId("")


def test_provider_id_too_long():
    """Test that provider ID longer than 64 characters raises ValueError."""
    with pytest.raises(ValueError, match="cannot exceed 64 characters"):
        ProviderId("a" * 65)


def test_provider_id_invalid_characters():
    """Test that provider ID with invalid characters raises ValueError."""
    with pytest.raises(ValueError, match="must contain only alphanumeric"):
        ProviderId("math provider")  # space not allowed


def test_provider_id_equality():
    """Test provider ID equality."""
    pid1 = ProviderId("math-provider")
    pid2 = ProviderId("math-provider")
    pid3 = ProviderId("other-provider")

    assert pid1 == pid2
    assert pid1 != pid3
    assert pid1 == "math-provider"  # String comparison supported for convenience


def test_provider_id_hashable():
    """Test that provider ID is hashable."""
    pid1 = ProviderId("math-provider")
    pid2 = ProviderId("math-provider")
    pid3 = ProviderId("other-provider")

    assert hash(pid1) == hash(pid2)
    assert hash(pid1) != hash(pid3)

    # Can be used in sets and dicts
    provider_set = {pid1, pid2, pid3}
    assert len(provider_set) == 2


# ToolName Tests


def test_tool_name_valid():
    """Test valid tool name."""
    tool = ToolName("calculate")
    assert tool.value == "calculate"
    assert str(tool) == "calculate"


def test_tool_name_empty():
    """Test that empty tool name raises ValueError."""
    with pytest.raises(ValueError, match="cannot be empty"):
        ToolName("")


def test_tool_name_too_long():
    """Test that tool name longer than 128 characters raises ValueError."""
    with pytest.raises(ValueError, match="cannot exceed 128 characters"):
        ToolName("a" * 129)


def test_tool_name_invalid_characters():
    """Test that tool name with invalid characters raises ValueError."""
    with pytest.raises(ValueError, match="must contain only alphanumeric"):
        ToolName("calculate!")


# ProviderMode Tests


def test_provider_mode_values():
    """Test provider mode enum values."""
    assert ProviderMode.SUBPROCESS.value == "subprocess"
    assert ProviderMode.DOCKER.value == "docker"
    assert ProviderMode.REMOTE.value == "remote"


def test_provider_mode_string():
    """Test provider mode string representation."""
    assert str(ProviderMode.SUBPROCESS) == "subprocess"
    assert str(ProviderMode.DOCKER) == "docker"


# CommandLine Tests


def test_command_line_basic():
    """Test basic command line."""
    cmd = CommandLine("python", "-m", "math_provider")
    assert cmd.command == "python"
    assert cmd.arguments == ("-m", "math_provider")
    assert cmd.to_list() == ["python", "-m", "math_provider"]


def test_command_line_no_arguments():
    """Test command line with no arguments."""
    cmd = CommandLine("python")
    assert cmd.command == "python"
    assert cmd.arguments == ()
    assert cmd.to_list() == ["python"]


def test_command_line_from_list():
    """Test creating command line from list."""
    cmd = CommandLine.from_list(["python", "-m", "math_provider"])
    assert cmd.command == "python"
    assert cmd.arguments == ("-m", "math_provider")


def test_command_line_from_empty_list():
    """Test that creating from empty list raises ValueError."""
    with pytest.raises(ValueError, match="cannot be empty"):
        CommandLine.from_list([])


def test_command_line_empty_command():
    """Test that empty command raises ValueError."""
    with pytest.raises(ValueError, match="cannot be empty"):
        CommandLine("")


def test_command_line_immutable():
    """Test that command line is immutable."""
    cmd = CommandLine("python", "-m", "math_provider")
    with pytest.raises(AttributeError):
        cmd.command = "node"


def test_command_line_string():
    """Test command line string representation."""
    cmd = CommandLine("python", "-m", "math_provider")
    assert str(cmd) == "python -m math_provider"


# DockerImage Tests


def test_docker_image_valid():
    """Test valid docker image."""
    img = DockerImage("python:3.11")
    assert img.value == "python:3.11"
    assert img.name == "python"
    assert img.tag == "3.11"


def test_docker_image_no_tag():
    """Test docker image without tag defaults to latest."""
    img = DockerImage("python")
    assert img.name == "python"
    assert img.tag == "latest"


def test_docker_image_with_registry():
    """Test docker image with registry."""
    img = DockerImage("ghcr.io/owner/image:v1.0")
    assert img.value == "ghcr.io/owner/image:v1.0"
    assert img.name == "ghcr.io/owner/image"
    assert img.tag == "v1.0"


def test_docker_image_empty():
    """Test that empty docker image raises ValueError."""
    with pytest.raises(ValueError, match="cannot be empty"):
        DockerImage("")


def test_docker_image_invalid_format():
    """Test that invalid docker image format raises ValueError."""
    with pytest.raises(ValueError, match="Invalid docker image format"):
        DockerImage("invalid image!")


# Endpoint Tests


def test_endpoint_http():
    """Test HTTP endpoint."""
    ep = Endpoint("http://localhost:8080")
    assert ep.value == "http://localhost:8080"
    assert ep.scheme == "http"
    assert ep.host == "localhost:8080"


def test_endpoint_https():
    """Test HTTPS endpoint."""
    ep = Endpoint("https://api.example.com/mcp")
    assert ep.scheme == "https"
    assert ep.host == "api.example.com"


def test_endpoint_websocket():
    """Test WebSocket endpoint."""
    ep = Endpoint("ws://localhost:9000")
    assert ep.scheme == "ws"


def test_endpoint_secure_websocket():
    """Test secure WebSocket endpoint."""
    ep = Endpoint("wss://api.example.com/ws")
    assert ep.scheme == "wss"


def test_endpoint_empty():
    """Test that empty endpoint raises ValueError."""
    with pytest.raises(ValueError, match="cannot be empty"):
        Endpoint("")


def test_endpoint_no_scheme():
    """Test that endpoint without scheme raises ValueError."""
    with pytest.raises(ValueError, match="must include scheme"):
        Endpoint("localhost:8080")


def test_endpoint_unsupported_scheme():
    """Test that unsupported scheme raises ValueError."""
    with pytest.raises(ValueError, match="Unsupported endpoint scheme"):
        Endpoint("ftp://localhost:21")


def test_endpoint_no_host():
    """Test that endpoint without host raises ValueError."""
    with pytest.raises(ValueError, match="must include host"):
        Endpoint("http://")


# EnvironmentVariables Tests


def test_environment_variables_empty():
    """Test empty environment variables."""
    env = EnvironmentVariables()
    assert len(env) == 0
    assert env.to_dict() == {}


def test_environment_variables_with_values():
    """Test environment variables with values."""
    env = EnvironmentVariables({"PATH": "/usr/bin", "USER": "test"})
    assert len(env) == 2
    assert env["PATH"] == "/usr/bin"
    assert env.get("USER") == "test"
    assert "PATH" in env


def test_environment_variables_get_default():
    """Test get with default value."""
    env = EnvironmentVariables({"PATH": "/usr/bin"})
    assert env.get("MISSING", "default") == "default"
    assert env.get("PATH", "default") == "/usr/bin"


def test_environment_variables_empty_key():
    """Test that empty key raises ValueError."""
    with pytest.raises(ValueError, match="key cannot be empty"):
        EnvironmentVariables({"": "value"})


def test_environment_variables_immutable():
    """Test that environment variables dict is immutable."""
    env = EnvironmentVariables({"PATH": "/usr/bin"})
    env_dict = env.to_dict()
    env_dict["NEW"] = "value"

    # Original should be unchanged
    assert "NEW" not in env


# IdleTTL Tests


def test_idle_ttl_valid():
    """Test valid idle TTL."""
    ttl = IdleTTL(300)
    assert ttl.seconds == 300
    assert int(ttl) == 300
    assert str(ttl) == "300s"


def test_idle_ttl_zero():
    """Test that zero TTL raises ValueError."""
    with pytest.raises(ValueError, match="must be positive"):
        IdleTTL(0)


def test_idle_ttl_negative():
    """Test that negative TTL raises ValueError."""
    with pytest.raises(ValueError, match="must be positive"):
        IdleTTL(-10)


def test_idle_ttl_too_large():
    """Test that TTL exceeding 1 day raises ValueError."""
    with pytest.raises(ValueError, match="cannot exceed 86400"):
        IdleTTL(86401)


# HealthCheckInterval Tests


def test_health_check_interval_valid():
    """Test valid health check interval."""
    interval = HealthCheckInterval(60)
    assert interval.seconds == 60
    assert int(interval) == 60
    assert str(interval) == "60s"


def test_health_check_interval_too_small():
    """Test that interval less than 5 seconds raises ValueError."""
    with pytest.raises(ValueError, match="must be at least 5 seconds"):
        HealthCheckInterval(4)


def test_health_check_interval_too_large():
    """Test that interval exceeding 1 hour raises ValueError."""
    with pytest.raises(ValueError, match="cannot exceed 3600"):
        HealthCheckInterval(3601)


# MaxConsecutiveFailures Tests


def test_max_consecutive_failures_valid():
    """Test valid max consecutive failures."""
    max_failures = MaxConsecutiveFailures(3)
    assert max_failures.count == 3
    assert int(max_failures) == 3
    assert str(max_failures) == "3"


def test_max_consecutive_failures_zero():
    """Test that zero failures raises ValueError."""
    with pytest.raises(ValueError, match="must be positive"):
        MaxConsecutiveFailures(0)


def test_max_consecutive_failures_too_large():
    """Test that failures exceeding 100 raises ValueError."""
    with pytest.raises(ValueError, match="cannot exceed 100"):
        MaxConsecutiveFailures(101)


# CorrelationId Tests


def test_correlation_id_generated():
    """Test that correlation ID is generated if not provided."""
    cid = CorrelationId()
    assert cid.value
    # Should be valid UUID
    uuid.UUID(cid.value, version=4)


def test_correlation_id_provided():
    """Test correlation ID with provided value."""
    test_uuid = str(uuid.uuid4())
    cid = CorrelationId(test_uuid)
    assert cid.value == test_uuid


def test_correlation_id_invalid():
    """Test that invalid UUID raises ValueError."""
    with pytest.raises(ValueError, match="must be a valid UUID"):
        CorrelationId("not-a-uuid")


def test_correlation_id_empty():
    """Test that empty correlation ID raises ValueError."""
    with pytest.raises(ValueError, match="cannot be empty"):
        CorrelationId("")


def test_correlation_id_unique():
    """Test that generated correlation IDs are unique."""
    cid1 = CorrelationId()
    cid2 = CorrelationId()
    assert cid1.value != cid2.value


# TimeoutSeconds Tests


def test_timeout_seconds_valid():
    """Test valid timeout."""
    timeout = TimeoutSeconds(30)
    assert timeout.seconds == 30.0
    assert float(timeout) == 30.0
    assert str(timeout) == "30.0s"


def test_timeout_seconds_float():
    """Test timeout with float value."""
    timeout = TimeoutSeconds(1.5)
    assert timeout.seconds == 1.5


def test_timeout_seconds_zero():
    """Test that zero timeout raises ValueError."""
    with pytest.raises(ValueError, match="must be positive"):
        TimeoutSeconds(0)


def test_timeout_seconds_negative():
    """Test that negative timeout raises ValueError."""
    with pytest.raises(ValueError, match="must be positive"):
        TimeoutSeconds(-5)


def test_timeout_seconds_too_large():
    """Test that timeout exceeding 1 hour raises ValueError."""
    with pytest.raises(ValueError, match="cannot exceed 3600"):
        TimeoutSeconds(3601)
