"""Server catalog and registry - manages the database of registered MCP servers."""

from __future__ import annotations

import json
import logging
from datetime import datetime
from pathlib import Path

import aiosqlite
import yaml

from mcp_bridge.config.settings import get_settings
from mcp_bridge.registry.models import ServerCategory, ServerEntry, ToolFilter

logger = logging.getLogger(__name__)


class ServerCatalog:
    """
    Manages the registry of available MCP servers.

    Backed by SQLite for persistence with YAML files for pre-built catalog entries.
    Supports adding, removing, enabling/disabling servers, and importing from
    MCP JSON config files or GitHub repositories.
    """

    def __init__(self, db_path: Path | None = None):
        self._db_path = db_path or get_settings().db_path
        self._db: aiosqlite.Connection | None = None

    async def initialize(self) -> None:
        """Initialize the database and load catalog files."""
        self._db = await aiosqlite.connect(str(self._db_path))
        self._db.row_factory = aiosqlite.Row
        await self._create_tables()
        await self._load_catalog_files()
        logger.info("Server catalog initialized with database at %s", self._db_path)

    async def close(self) -> None:
        """Close the database connection."""
        if self._db:
            await self._db.close()
            self._db = None

    async def _create_tables(self) -> None:
        """Create the servers table if it doesn't exist."""
        assert self._db is not None
        await self._db.execute("""
            CREATE TABLE IF NOT EXISTS servers (
                id TEXT PRIMARY KEY,
                name TEXT NOT NULL,
                description TEXT DEFAULT '',
                category TEXT DEFAULT 'custom',
                version TEXT DEFAULT '0.0.0',
                tags TEXT DEFAULT '[]',
                command TEXT NOT NULL,
                args TEXT DEFAULT '[]',
                transport TEXT DEFAULT 'stdio',
                port INTEGER,
                endpoint TEXT,
                cwd TEXT,
                env TEXT DEFAULT '{}',
                required_env TEXT DEFAULT '[]',
                configurable_options TEXT DEFAULT '[]',
                max_instances INTEGER DEFAULT 5,
                auto_shutdown_idle_seconds INTEGER,
                tool_filter TEXT DEFAULT '{}',
                source TEXT DEFAULT 'catalog',
                enabled INTEGER DEFAULT 1,
                created_at TEXT,
                updated_at TEXT
            )
        """)
        await self._db.commit()

    async def _load_catalog_files(self) -> None:
        """Load server entries from YAML catalog files."""
        settings = get_settings()
        catalog_dir = settings.resolved_catalog_dir

        if not catalog_dir.exists():
            logger.debug("No catalog directory found at %s", catalog_dir)
            return

        for yaml_file in sorted(catalog_dir.glob("*.yaml")):
            try:
                with open(yaml_file) as f:
                    data = yaml.safe_load(f)

                if not data or "servers" not in data:
                    continue

                for server_data in data["servers"]:
                    server = ServerEntry(**server_data)
                    existing = await self.get_server(server.id)
                    if existing is None:
                        await self._insert_server(server)
                        logger.debug("Loaded server '%s' from catalog file %s", server.id, yaml_file.name)
                    elif existing.source == "catalog":
                        # Re-sync catalog entries so YAML edits take effect
                        await self._insert_server(server)
                        logger.debug("Updated catalog server '%s' from %s", server.id, yaml_file.name)

            except Exception as e:
                logger.warning("Failed to load catalog file %s: %s", yaml_file.name, e)

    async def _insert_server(self, server: ServerEntry) -> None:
        """Insert a server entry into the database."""
        assert self._db is not None
        await self._db.execute(
            """
            INSERT OR REPLACE INTO servers
            (id, name, description, category, version, tags, command, args,
             transport, port, endpoint, cwd, env, required_env,
             configurable_options, max_instances, auto_shutdown_idle_seconds,
             tool_filter, source, enabled, created_at, updated_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                server.id,
                server.name,
                server.description,
                server.category.value,
                server.version,
                json.dumps(server.tags),
                server.command,
                json.dumps(server.args),
                server.transport.value,
                server.port,
                server.endpoint,
                server.cwd,
                json.dumps(server.env),
                json.dumps(server.required_env),
                json.dumps([opt.model_dump() for opt in server.configurable_options]),
                server.max_instances,
                server.auto_shutdown_idle_seconds,
                server.tool_filter.model_dump_json(),
                server.source,
                1 if server.enabled else 0,
                server.created_at.isoformat(),
                server.updated_at.isoformat(),
            ),
        )
        await self._db.commit()

    def _row_to_entry(self, row: aiosqlite.Row) -> ServerEntry:
        """Convert a database row to a ServerEntry."""
        return ServerEntry(
            id=row["id"],
            name=row["name"],
            description=row["description"],
            category=ServerCategory(row["category"]),
            version=row["version"],
            tags=json.loads(row["tags"]),
            command=row["command"],
            args=json.loads(row["args"]),
            transport=row["transport"],
            port=row["port"],
            endpoint=row["endpoint"],
            cwd=row["cwd"],
            env=json.loads(row["env"]),
            required_env=json.loads(row["required_env"]),
            configurable_options=json.loads(row["configurable_options"]),
            max_instances=row["max_instances"],
            auto_shutdown_idle_seconds=row["auto_shutdown_idle_seconds"],
            tool_filter=ToolFilter(**json.loads(row["tool_filter"])),
            source=row["source"],
            enabled=bool(row["enabled"]),
            created_at=datetime.fromisoformat(row["created_at"]) if row["created_at"] else datetime.now(),
            updated_at=datetime.fromisoformat(row["updated_at"]) if row["updated_at"] else datetime.now(),
        )

    # --- Public API ---

    async def get_server(self, server_id: str) -> ServerEntry | None:
        """Get a server entry by ID."""
        assert self._db is not None
        async with self._db.execute("SELECT * FROM servers WHERE id = ?", (server_id,)) as cursor:
            row = await cursor.fetchone()
            if row:
                return self._row_to_entry(row)
        return None

    async def list_servers(
        self,
        category: str | None = None,
        tag: str | None = None,
        enabled_only: bool = True,
        search: str | None = None,
    ) -> list[ServerEntry]:
        """List servers with optional filtering."""
        assert self._db is not None
        query = "SELECT * FROM servers WHERE 1=1"
        params: list = []

        if enabled_only:
            query += " AND enabled = 1"
        if category:
            query += " AND category = ?"
            params.append(category)
        if search:
            query += " AND (name LIKE ? OR description LIKE ? OR tags LIKE ?)"
            pattern = f"%{search}%"
            params.extend([pattern, pattern, pattern])

        query += " ORDER BY category, name"

        servers = []
        async with self._db.execute(query, params) as cursor:
            async for row in cursor:
                entry = self._row_to_entry(row)
                if tag and tag not in entry.tags:
                    continue
                servers.append(entry)

        return servers

    async def list_categories(self) -> list[dict]:
        """List all categories with their server counts."""
        assert self._db is not None
        categories = []
        async with self._db.execute(
            "SELECT category, COUNT(*) as count FROM servers WHERE enabled = 1 GROUP BY category ORDER BY category"
        ) as cursor:
            async for row in cursor:
                categories.append({"category": row["category"], "count": row["count"]})
        return categories

    async def register_server(self, server: ServerEntry) -> ServerEntry:
        """Register a new server or update an existing one."""
        server.updated_at = datetime.now()
        await self._insert_server(server)
        logger.info("Registered server: %s (%s)", server.id, server.name)
        return server

    async def unregister_server(self, server_id: str) -> bool:
        """Remove a server from the registry."""
        assert self._db is not None
        result = await self._db.execute("DELETE FROM servers WHERE id = ?", (server_id,))
        await self._db.commit()
        deleted = result.rowcount > 0
        if deleted:
            logger.info("Unregistered server: %s", server_id)
        return deleted

    async def enable_server(self, server_id: str) -> bool:
        """Enable a server."""
        return await self._set_enabled(server_id, True)

    async def disable_server(self, server_id: str) -> bool:
        """Disable a server."""
        return await self._set_enabled(server_id, False)

    async def _set_enabled(self, server_id: str, enabled: bool) -> bool:
        """Set the enabled state of a server."""
        assert self._db is not None
        result = await self._db.execute(
            "UPDATE servers SET enabled = ?, updated_at = ? WHERE id = ?",
            (1 if enabled else 0, datetime.now().isoformat(), server_id),
        )
        await self._db.commit()
        return result.rowcount > 0

    async def update_tool_filter(self, server_id: str, tool_filter: ToolFilter) -> bool:
        """Update the tool filter for a server."""
        assert self._db is not None
        result = await self._db.execute(
            "UPDATE servers SET tool_filter = ?, updated_at = ? WHERE id = ?",
            (tool_filter.model_dump_json(), datetime.now().isoformat(), server_id),
        )
        await self._db.commit()
        return result.rowcount > 0

    async def import_from_mcp_json(self, config_data: dict, source: str = "import") -> list[ServerEntry]:
        """
        Import servers from an MCP JSON config format.

        Supports the standard format:
        {
            "mcpServers": {
                "server-name": {
                    "command": "...",
                    "args": [...],
                    "env": {...}
                }
            }
        }
        """
        imported = []
        mcp_servers = config_data.get("mcpServers", {})

        for server_name, server_config in mcp_servers.items():
            server_id = server_name.lower().replace(" ", "-").replace("_", "-")

            # Parse command and args
            command = server_config.get("command", "")
            args = server_config.get("args", [])
            env = server_config.get("env", {})

            # Detect transport
            transport = "stdio"
            if "url" in server_config:
                transport = "http" if "http" in server_config["url"] else "sse"

            entry = ServerEntry(
                id=server_id,
                name=server_name,
                description=f"Imported from {source}",
                category=ServerCategory.CUSTOM,
                command=command,
                args=args,
                transport=transport,
                env=env,
                source=source,
            )

            await self.register_server(entry)
            imported.append(entry)

        logger.info("Imported %d servers from MCP JSON config", len(imported))
        return imported

    async def get_server_count(self) -> int:
        """Get total number of registered servers."""
        assert self._db is not None
        async with self._db.execute("SELECT COUNT(*) FROM servers") as cursor:
            row = await cursor.fetchone()
            return row[0] if row else 0
