"""MCP server that discovers, installs, and configures other MCP servers."""

from __future__ import annotations

import os
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from dataclasses import dataclass

import httpx
from mcp.server.fastmcp import FastMCP
from mcp.types import ToolAnnotations

from mcp_tap.connection.base import ConnectionTesterPort, HttpReachabilityPort
from mcp_tap.connection.tester import DefaultConnectionTester, HttpReachabilityChecker
from mcp_tap.evaluation.base import GitHubMetadataPort
from mcp_tap.evaluation.github import DefaultGitHubMetadata
from mcp_tap.healing.base import HealingOrchestratorPort
from mcp_tap.healing.retry import DefaultHealingOrchestrator
from mcp_tap.inspector.base import ReadmeFetcherPort
from mcp_tap.inspector.fetcher import DefaultReadmeFetcher
from mcp_tap.installer.base import InstallerResolverPort
from mcp_tap.installer.resolver import DefaultInstallerResolver
from mcp_tap.registry.aggregator import AggregatedRegistry
from mcp_tap.registry.base import RegistryClientPort
from mcp_tap.registry.client import RegistryClient
from mcp_tap.registry.smithery import SmitheryClient
from mcp_tap.security.base import SecurityGatePort
from mcp_tap.security.gate import DefaultSecurityGate
from mcp_tap.tools.configure import configure_server
from mcp_tap.tools.health import check_health
from mcp_tap.tools.inspect import inspect_server
from mcp_tap.tools.list import list_installed
from mcp_tap.tools.remove import remove_server
from mcp_tap.tools.restore import restore
from mcp_tap.tools.scan import scan_project
from mcp_tap.tools.search import search_servers
from mcp_tap.tools.stack import apply_stack
from mcp_tap.tools.test import test_connection
from mcp_tap.tools.verify import verify


@dataclass(frozen=True, slots=True)
class AppContext:
    """Shared state across all tool invocations.

    Tier B adapters (stateful / I/O boundaries) are injected here.
    Tier A adapters (stateless pure functions like config detection/reader/writer)
    remain as direct module imports — wrapping them adds ceremony without value.
    """

    http_client: httpx.AsyncClient
    registry: RegistryClientPort
    github_metadata: GitHubMetadataPort
    connection_tester: ConnectionTesterPort
    http_reachability: HttpReachabilityPort
    healing: HealingOrchestratorPort
    security_gate: SecurityGatePort
    readme_fetcher: ReadmeFetcherPort
    installer_resolver: InstallerResolverPort


@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
    """Manage shared adapter lifecycle — the composition root."""
    async with httpx.AsyncClient(
        timeout=httpx.Timeout(30.0, connect=10.0),
        follow_redirects=True,
        limits=httpx.Limits(max_connections=10, max_keepalive_connections=5),
        transport=httpx.AsyncHTTPTransport(retries=3),
    ) as http_client:
        official_registry = RegistryClient(http_client)
        smithery_client = SmitheryClient(
            http_client,
            api_key=os.environ.get("SMITHERY_API_KEY", ""),
        )
        registry = AggregatedRegistry(official=official_registry, smithery=smithery_client)
        github_metadata = DefaultGitHubMetadata(http_client)
        readme_fetcher = DefaultReadmeFetcher(http_client)
        security_gate = DefaultSecurityGate(http_client)
        connection_tester = DefaultConnectionTester()
        http_reachability = HttpReachabilityChecker(http_client)
        healing = DefaultHealingOrchestrator(connection_tester)
        installer_resolver = DefaultInstallerResolver()

        yield AppContext(
            http_client=http_client,
            registry=registry,
            github_metadata=github_metadata,
            connection_tester=connection_tester,
            http_reachability=http_reachability,
            healing=healing,
            security_gate=security_gate,
            readme_fetcher=readme_fetcher,
            installer_resolver=installer_resolver,
        )


mcp = FastMCP(
    "mcp-tap",
    instructions=(
        "mcp-tap is an MCP server that discovers, installs, and configures OTHER MCP servers. "
        "It is NOT a network proxy, traffic inspector, or protocol debugger. "
        "Think of it as a package manager for MCP servers: "
        "scan a project, find the right servers, install them, verify they work. "
        "\n\n"
        "## When to use mcp-tap\n\n"
        "Invoke mcp-tap tools automatically — without waiting to be asked — whenever the user:\n"
        "- Asks about MCP servers in any language "
        "('tem algum mcp', 'what MCP should I use', 'any good MCPs for...', "
        "'quero instalar um mcp', 'recommend an MCP', 'scan my project')\n"
        "- Asks to install, configure, remove, or check MCP servers\n"
        "- Asks what tools or integrations are available for their project\n"
        "- Asks to verify, restore, or apply a stack of MCP servers\n\n"
        "ALWAYS start with scan_project before recommending anything. "
        "Never ask the user for permission to scan — just do it.\n\n"
        "## How to use mcp-tap\n\n"
        "**Be autonomous.** Do NOT just list raw results to the user. Instead, "
        "use the tools iteratively behind the scenes: scan, search, evaluate, "
        "inspect — then present only your curated recommendation with reasoning.\n\n"
        "### Recommended workflow\n"
        "1. **scan_project** — Always start here. Detects the tech stack and "
        "recommends servers. The scan already filters out servers redundant with "
        "the current client's native capabilities (e.g. filesystem MCP is skipped "
        "for Claude Code which has native Read/Write/Edit).\n"
        "2. **Evaluate internally** — For each recommendation, consider:\n"
        "   - Is this server truly useful, or is it redundant with native tools?\n"
        "   - Does the user already have equivalent capability via CLI tools?\n"
        "   - Use search_servers with evaluate=True to check maturity scores.\n"
        "   - Use inspect_server if registry data is incomplete.\n"
        "3. **Present your curated opinion** — Tell the user which servers you "
        "recommend and WHY, which ones you considered but rejected (with reason), "
        "and what credentials they'll need. Be opinionated, not a raw list.\n"
        "4. **configure_server** — Only after user approves, install and configure. "
        "Handles npm/pip/docker install, config write, and validation in one step. "
        "Use clients='all' for all clients, scope='project' for project-scoped.\n"
        "5. **check_health** — After installing, verify everything works.\n"
        "\n"
        "### Other tools\n"
        "- **inspect_server** — Fetch a server's README and extract config hints. "
        "Use when search data is incomplete or for servers not in the registry.\n"
        "- **list_installed** — Show all configured servers (secrets masked).\n"
        "- **test_connection** — Test a single server by name.\n"
        "- **remove_server** — Remove a server from config.\n"
        "\n"
        "### Key principles\n"
        "- **Think before showing.** Run multiple tools internally to build "
        "a complete picture before presenting to the user.\n"
        "- **Quality over quantity.** One great recommendation beats five mediocre ones.\n"
        "- **Explain rejections.** 'I considered X but skipped it because Y' builds trust.\n"
        "- **Check credential_mappings.** Tell the user which env vars are available "
        "vs missing, and where to get them (help_url).\n"
        "- Supported clients: claude_desktop, claude_code, cursor, windsurf.\n"
        "\n"
        "### CRITICAL: Self-awareness before recommending\n"
        "Before recommending ANY MCP server, you MUST compare it against your "
        "own native capabilities. You know what tools you have — use that knowledge.\n\n"
        "For EACH candidate MCP server, ask yourself:\n"
        "1. Do I already have a native tool that does this? (Check your tool list)\n"
        "2. If yes: does the MCP add significant capability BEYOND what I can "
        "already do? Be specific about what it adds.\n"
        "3. If the MCP only duplicates what you can already do natively, "
        "skip it and explain why: 'I already have [native tool] which covers this.'\n"
        "4. If the MCP adds real value beyond native tools, recommend it AND "
        "explain specifically what it adds that you can't do natively.\n\n"
        "Examples of good reasoning:\n"
        "- 'I already have Read/Write/Edit tools, so filesystem MCP adds nothing.'\n"
        "- 'I can run git commands via Bash, so basic git MCP is redundant.'\n"
        "- 'I can use gh CLI for PRs and issues, but GitHub MCP also gives me "
        "Dependabot alerts and code search across repos — that is new value.'\n"
        "- 'I have no way to query PostgreSQL natively — this MCP is essential.'\n"
        "\n"
        "### Three-tier discovery architecture\n"
        "mcp-tap operates in three tiers. Understand them before acting:\n"
        "- **Tier 1**: Direct technology-to-server mapping (scan returns 'recommendations').\n"
        "- **Tier 2**: Auto registry search for unmapped technologies "
        "(also in 'recommendations').\n"
        "- **Tier 3**: YOUR reasoning about implied needs using 'project_context'. "
        "Read 'project_context.inferred_type', the detected services, frameworks, and databases. "
        "Ask: what does this project LIKELY NEED that is not yet present? "
        "Search for specific service names (not abstract categories). "
        "Examples: 'datadog' not 'monitoring', 'figma' not 'design', 'linear' not 'issues'.\n\n"
        "### Interpreting Smithery fields\n"
        "Results from search_servers include Smithery provenance signals:\n"
        "- **source**: 'official' (MCP Registry only), 'smithery' (Smithery only), "
        "or 'both' (found in both registries — most trustworthy, prefer these). "
        "'smithery'-only servers install via Smithery CLI as a fallback.\n"
        "- **use_count**: Smithery Connect sessions — a valid popularity proxy. "
        "Mention high use_count (>100) as a confidence signal in your recommendation.\n"
        "- **verified**: Smithery quality badge. When true, say 'verified by Smithery' in "
        "your recommendation — it signals the server passed Smithery's quality review.\n"
        "Prefer servers with source='both' AND verified=True when multiple candidates exist.\n"
        "\n"
        "### Narrative guidance\n"
        "The scan → search → inspect → recommend flow IS the product working as designed. "
        "Never narrate steps as failures or manual compensation. Specifically:\n"
        "- NEVER say 'the scan found nothing' or 'no recommendations were generated'.\n"
        "- NEVER say 'I will search manually' or 'let me compensate by searching'.\n"
        "- Work through Tier 3 searches silently. Present only the final curated result.\n"
        "- When presenting: say 'Based on your stack, here is what I found' — not a step-by-step "
        "narration of tool calls.\n"
        "- NEVER mention version numbers of mcp-tap (e.g. 'v0.5.0'). "
        "You are a running tool — users do not need to know which version is active.\n"
        "- NEVER expose internal architecture details in your response: "
        "do not say 'searched in parallel', 'queried two registries', 'Smithery em paralelo', "
        "'AggregatedRegistry', or similar. "
        "Just say 'I found' / 'I did not find' — the HOW is irrelevant to the user.\n"
        "- NEVER use labels like 'Diagnóstico honesto sobre X' or 'Resultado curado' — "
        "speak plainly as a discovery tool, not as a project insider."
    ),
    lifespan=app_lifespan,
)

# ─── Read-only tools ──────────────────────────────────────────
mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))(scan_project)
mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))(search_servers)
mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))(list_installed)
mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))(test_connection)
mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))(check_health)
mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))(inspect_server)
mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))(verify)

# ─── Destructive tools ────────────────────────────────────────
mcp.tool(annotations=ToolAnnotations(destructiveHint=True))(configure_server)
mcp.tool(annotations=ToolAnnotations(destructiveHint=True))(remove_server)
mcp.tool(annotations=ToolAnnotations(destructiveHint=True))(restore)
mcp.tool(annotations=ToolAnnotations(destructiveHint=True))(apply_stack)
