"""Domain models for mcp-tap. All frozen dataclasses -- no mutation after creation."""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import StrEnum

# ─── Enumerations ─────────────────────────────────────────────


class Transport(StrEnum):
    STDIO = "stdio"
    SSE = "sse"
    STREAMABLE_HTTP = "streamable-http"


class RegistryType(StrEnum):
    NPM = "npm"
    PYPI = "pypi"
    OCI = "oci"


class MCPClient(StrEnum):
    CLAUDE_DESKTOP = "claude_desktop"
    CLAUDE_CODE = "claude_code"
    CURSOR = "cursor"
    WINDSURF = "windsurf"


# ─── Registry Models ──────────────────────────────────────────


@dataclass(frozen=True, slots=True)
class EnvVarSpec:
    """An environment variable required by an MCP server package."""

    name: str
    description: str = ""
    is_required: bool = True
    is_secret: bool = False


@dataclass(frozen=True, slots=True)
class PackageInfo:
    """A specific package distribution of an MCP server."""

    registry_type: RegistryType
    identifier: str
    version: str = ""
    transport: Transport = Transport.STDIO
    environment_variables: list[EnvVarSpec] = field(default_factory=list)


@dataclass(frozen=True, slots=True)
class RegistryServer:
    """An MCP server as returned by the MCP Registry API."""

    name: str
    description: str
    version: str = ""
    repository_url: str = ""
    packages: list[PackageInfo] = field(default_factory=list)
    is_official: bool = False
    updated_at: str = ""


# ─── Config Models ────────────────────────────────────────────


@dataclass(frozen=True, slots=True)
class ServerConfig:
    """A single MCP server entry in a client config file."""

    command: str
    args: list[str] = field(default_factory=list)
    env: dict[str, str] = field(default_factory=dict)

    def to_dict(self) -> dict[str, object]:
        result: dict[str, object] = {"command": self.command, "args": list(self.args)}
        if self.env:
            result["env"] = dict(self.env)
        return result


@dataclass(frozen=True, slots=True)
class ConfigLocation:
    """Location of an MCP client's config file on disk."""

    client: MCPClient
    path: str
    scope: str  # "user" or "project"
    exists: bool


@dataclass(frozen=True, slots=True)
class InstalledServer:
    """An MCP server currently configured in a client config file."""

    name: str
    config: ServerConfig
    source_file: str


# ─── Tool Return Models ───────────────────────────────────────


@dataclass(frozen=True, slots=True)
class SearchResult:
    name: str
    description: str
    version: str
    registry_type: str
    package_identifier: str
    transport: str
    is_official: bool
    updated_at: str
    env_vars_required: list[str]
    repository_url: str


@dataclass(frozen=True, slots=True)
class InstallResult:
    success: bool
    package_identifier: str
    install_method: str
    message: str
    command_output: str = ""


@dataclass(frozen=True, slots=True)
class ConfigureResult:
    success: bool
    server_name: str
    config_file: str
    message: str
    config_written: dict[str, object] = field(default_factory=dict)
    install_status: str = ""  # installed / already_available / skipped / failed
    tools_discovered: list[str] = field(default_factory=list)
    validation_passed: bool = False


@dataclass(frozen=True, slots=True)
class ConnectionTestResult:
    success: bool
    server_name: str
    tools_discovered: list[str] = field(default_factory=list)
    error: str = ""


@dataclass(frozen=True, slots=True)
class RemoveResult:
    success: bool
    server_name: str
    config_file: str = ""
    uninstalled_package: bool = False
    message: str = ""


# ─── Health Check Models ─────────────────────────────────────


@dataclass(frozen=True, slots=True)
class ServerHealth:
    """Health status of a single configured MCP server."""

    name: str
    status: str  # "healthy", "unhealthy", "timeout"
    tools_count: int = 0
    tools: list[str] = field(default_factory=list)
    error: str = ""


@dataclass(frozen=True, slots=True)
class HealthReport:
    """Aggregated health report for all configured MCP servers."""

    client: str
    config_file: str
    total: int
    healthy: int
    unhealthy: int
    servers: list[ServerHealth] = field(default_factory=list)


# ─── Scanner Models ──────────────────────────────────────────


class TechnologyCategory(StrEnum):
    LANGUAGE = "language"
    FRAMEWORK = "framework"
    DATABASE = "database"
    SERVICE = "service"
    PLATFORM = "platform"


@dataclass(frozen=True, slots=True)
class DetectedTechnology:
    """A technology detected by scanning project files."""

    name: str
    category: TechnologyCategory
    source_file: str
    confidence: float = 1.0


@dataclass(frozen=True, slots=True)
class ServerRecommendation:
    """A recommended MCP server based on detected project technologies."""

    server_name: str
    package_identifier: str
    registry_type: RegistryType
    reason: str
    priority: str  # "high", "medium", "low"


@dataclass(frozen=True, slots=True)
class ProjectProfile:
    """Aggregated scan result describing a project's technology stack."""

    path: str
    technologies: list[DetectedTechnology] = field(default_factory=list)
    env_var_names: list[str] = field(default_factory=list)
    recommendations: list[ServerRecommendation] = field(default_factory=list)


# ─── Healing Models ──────────────────────────────────────────


class ErrorCategory(StrEnum):
    COMMAND_NOT_FOUND = "command_not_found"
    CONNECTION_REFUSED = "connection_refused"
    TIMEOUT = "timeout"
    AUTH_FAILED = "auth_failed"
    MISSING_ENV_VAR = "missing_env_var"
    TRANSPORT_MISMATCH = "transport_mismatch"
    PERMISSION_DENIED = "permission_denied"
    UNKNOWN = "unknown"


@dataclass(frozen=True, slots=True)
class DiagnosisResult:
    """Structured diagnosis of a server connection error."""

    category: ErrorCategory
    original_error: str
    explanation: str
    suggested_fix: str
    confidence: float = 1.0


@dataclass(frozen=True, slots=True)
class CandidateFix:
    """A proposed fix for a diagnosed server error."""

    description: str
    new_config: ServerConfig | None = None
    install_command: str | None = None
    env_var_hint: str | None = None
    requires_user_action: bool = False


@dataclass(frozen=True, slots=True)
class HealingAttempt:
    """A single diagnose-fix-retry iteration."""

    diagnosis: DiagnosisResult
    fix_applied: CandidateFix
    success: bool


@dataclass(frozen=True, slots=True)
class HealingResult:
    """Outcome of the full healing retry loop."""

    fixed: bool
    attempts: list[HealingAttempt] = field(default_factory=list)
    fixed_config: ServerConfig | None = None
    user_action_needed: str = ""


# ─── Credential Mapping Models ─────────────────────────────


@dataclass(frozen=True, slots=True)
class CredentialMapping:
    """Maps a server's required env var to an available project credential."""

    server_name: str
    required_env_var: str
    available_env_var: str | None = None
    source: str = "not found"
    status: str = "missing"  # "exact_match", "compatible_match", "missing"
    help_url: str = ""


# ─── Inspector Models ───────────────────────────────────────


@dataclass(frozen=True, slots=True)
class EnvVarHint:
    """An environment variable mentioned in server documentation."""

    name: str
    context: str
    is_required: bool = True


@dataclass(frozen=True, slots=True)
class ConfigHints:
    """Structured hints extracted from a server's documentation."""

    install_commands: list[str] = field(default_factory=list)
    transport_hints: list[str] = field(default_factory=list)
    env_vars_mentioned: list[EnvVarHint] = field(default_factory=list)
    command_patterns: list[str] = field(default_factory=list)
    json_config_blocks: list[str] = field(default_factory=list)
    confidence: float = 0.0


# ─── Evaluation Models ──────────────────────────────────────


@dataclass(frozen=True, slots=True)
class MaturitySignals:
    """Raw signals collected about a server's maturity/health."""

    stars: int | None = None
    forks: int | None = None
    open_issues: int | None = None
    last_commit_date: str | None = None
    last_release_date: str | None = None
    is_official: bool = False
    is_archived: bool = False
    license: str | None = None


@dataclass(frozen=True, slots=True)
class MaturityScore:
    """Computed maturity assessment for a server."""

    score: float
    tier: str  # "recommended", "acceptable", "caution", "avoid"
    reasons: list[str] = field(default_factory=list)
    warning: str | None = None
