"""Shared instance management tools for all eRegistrations MCP servers.

Provides instance_add, instance_list, instance_remove — registered once
and shared across BPA, DS, and GDB servers.
"""

from __future__ import annotations

import logging
from typing import Any
from urllib.parse import urlparse

from fastmcp.exceptions import ToolError
from mcp.types import ToolAnnotations

from mcp_eregistrations_common.config import (
    load_builtin_instances,
    load_profiles,
    save_profiles,
)

logger = logging.getLogger(__name__)

__all__ = [
    "instance_add",
    "instance_list",
    "instance_remove",
    "register_instance_tools",
    "validate_url",
]

# ToolAnnotation constants
READ = ToolAnnotations(readOnlyHint=True, destructiveHint=False, openWorldHint=True)
WRITE = ToolAnnotations(readOnlyHint=False, destructiveHint=False, openWorldHint=True)
DESTRUCTIVE = ToolAnnotations(
    readOnlyHint=False, destructiveHint=True, openWorldHint=True
)


# ---------------------------------------------------------------------------
# URL validation
# ---------------------------------------------------------------------------


def validate_url(url: str) -> str:
    """Validate and normalize an instance URL.

    Requires HTTPS, valid host, no query/fragment. Returns URL with
    trailing slash stripped.

    Args:
        url: URL to validate.

    Returns:
        Normalized URL string.

    Raises:
        ToolError: On invalid URL.
    """
    if not url or not url.strip():
        raise ToolError("URL is required and cannot be empty.")

    parsed = urlparse(url.strip())

    if not parsed.scheme:
        raise ToolError(
            f"Invalid URL '{url}': no scheme. Use HTTPS (e.g. https://example.com)."
        )

    if parsed.scheme != "https":
        raise ToolError(f"Invalid URL '{url}': must use HTTPS, not '{parsed.scheme}'.")

    if not parsed.hostname:
        raise ToolError(f"Invalid URL '{url}': no valid hostname.")

    if parsed.query:
        raise ToolError(
            f"Invalid URL '{url}': query strings not allowed. "
            "Provide the base URL only."
        )

    if parsed.fragment:
        raise ToolError(
            f"Invalid URL '{url}': fragment not allowed. Provide the base URL only."
        )

    return url.strip().rstrip("/")


# ---------------------------------------------------------------------------
# instance_list
# ---------------------------------------------------------------------------


async def instance_list() -> dict[str, Any]:
    """List all available instances (built-in + custom profiles).

    Returns:
        dict with total, profiles (name -> urls, auto_login, source).
    """
    # Lazy import to avoid circular dependency
    from mcp_eregistrations_common import auth_store

    builtin = load_builtin_instances()
    custom = load_profiles()

    profiles: dict[str, dict[str, Any]] = {}
    seen: set[str] = set()

    # Custom profiles override built-in by name
    for name, profile in custom.items():
        profiles[name] = {
            "bpa_url": profile.get("bpa_url"),
            "ds_url": profile.get("ds_url"),
            "gdb_url": profile.get("gdb_url"),
            "auto_login": auth_store.get_credentials(name) is not None,
            "source": "custom",
        }
        seen.add(name)

    # Built-in instances (not overridden)
    for inst in builtin:
        name = inst["name"]
        if name not in seen:
            profiles[name] = {
                "bpa_url": inst.get("bpa_url"),
                "ds_url": inst.get("ds_url"),
                "gdb_url": inst.get("gdb_url"),
                "auto_login": auth_store.get_credentials(name) is not None,
                "source": "builtin",
            }

    return {"total": len(profiles), "profiles": profiles}


# ---------------------------------------------------------------------------
# instance_add
# ---------------------------------------------------------------------------


async def instance_add(
    name: str,
    bpa_url: str | None = None,
    ds_url: str | None = None,
    gdb_url: str | None = None,
    keycloak_url: str | None = None,
    keycloak_realm: str | None = None,
    keycloak_client_id: str = "mcp-eregistrations-bpa",
    cas_url: str | None = None,
    cas_client_id: str | None = None,
    cas_client_secret: str | None = None,
    username: str | None = None,
    password: str | None = None,
) -> dict[str, Any]:
    """Register or update an eRegistrations instance profile.

    Args:
        name: Profile name (e.g. "jamaica", "staging").
        bpa_url: BPA instance URL (HTTPS).
        ds_url: Display System URL (HTTPS).
        gdb_url: GDB instance URL (HTTPS).
        keycloak_url: Keycloak server URL.
        keycloak_realm: Keycloak realm name.
        keycloak_client_id: Keycloak client ID (default: mcp-eregistrations-bpa).
        cas_url: CAS server URL for legacy auth.
        cas_client_id: CAS client ID (required if cas_url set).
        cas_client_secret: CAS client secret (required if cas_url set).
        username: Username for auto-login.
        password: Password for auto-login (required if username set).

    Returns:
        dict with name, bpa_url, ds_url, gdb_url, created, replaced, auto_login.
    """
    # --- Validate name ---
    if not name or not name.strip():
        raise ToolError("Profile name is required.")
    name = name.strip().lower()

    # --- Validate at least one URL ---
    if not any([bpa_url, ds_url, gdb_url]):
        raise ToolError("At least one URL is required (bpa_url, ds_url, or gdb_url).")

    # --- Validate URLs ---
    if bpa_url:
        bpa_url = validate_url(bpa_url)
    if ds_url:
        ds_url = validate_url(ds_url)
    if gdb_url:
        gdb_url = validate_url(gdb_url)
    if keycloak_url:
        keycloak_url = validate_url(keycloak_url)
    if cas_url:
        cas_url = validate_url(cas_url)

    # --- CAS all-or-nothing ---
    cas_fields = [cas_url, cas_client_id, cas_client_secret]
    cas_provided = [f for f in cas_fields if f]
    if cas_provided and len(cas_provided) != 3:
        missing = []
        if not cas_url:
            missing.append("cas_url")
        if not cas_client_id:
            missing.append("cas_client_id")
        if not cas_client_secret:
            missing.append("cas_client_secret")
        raise ToolError(
            f"CAS auth requires all three: cas_url, cas_client_id, cas_client_secret. "
            f"Missing: {', '.join(missing)}."
        )

    # --- Credential pair validation ---
    if username and not password:
        raise ToolError("Password is required when username is provided.")
    if password and not username:
        raise ToolError("Username is required when password is provided.")

    # --- Build profile dict ---
    profile: dict[str, Any] = {}
    if bpa_url:
        profile["bpa_url"] = bpa_url
    if ds_url:
        profile["ds_url"] = ds_url
    if gdb_url:
        profile["gdb_url"] = gdb_url
    if keycloak_url:
        profile["keycloak_url"] = keycloak_url
    if keycloak_realm:
        profile["keycloak_realm"] = keycloak_realm
    if keycloak_client_id != "mcp-eregistrations-bpa":
        profile["keycloak_client_id"] = keycloak_client_id
    if cas_url:
        profile["cas_url"] = cas_url
    if cas_client_id:
        profile["cas_client_id"] = cas_client_id
    # CAS secret goes to auth_store, NOT in profile

    # --- Store CAS secret in auth_store (not in profile) ---
    if cas_client_secret:
        from mcp_eregistrations_common import auth_store

        auth_store.store_cas_secret(name, cas_client_secret)

    # --- Store credentials in auth_store ---
    auto_login: str | None = None
    if username and password:
        from mcp_eregistrations_common import auth_store

        try:
            auth_store.store_credentials(
                name,
                username,
                password,
                keycloak_url=keycloak_url,
                keycloak_realm=keycloak_realm,
            )
            auto_login = "enabled"

            # Best-effort Keycloak verification
            if keycloak_url and keycloak_realm:
                try:
                    from mcp_eregistrations_common.auth import (
                        keycloak_password_grant,
                    )

                    await keycloak_password_grant(
                        keycloak_url=keycloak_url,
                        realm=keycloak_realm,
                        client_id=keycloak_client_id,
                        username=username,
                        password=password,
                    )
                    auto_login = "enabled_and_verified"
                except Exception:
                    auto_login = "enabled_not_verified"
        except Exception:
            auto_login = "store_failed"

    # --- Save profile ---
    profiles = load_profiles()
    replaced = name in profiles
    profiles[name] = profile
    save_profiles(profiles)

    # --- Build result ---
    result: dict[str, Any] = {
        "name": name,
        "bpa_url": bpa_url,
        "ds_url": ds_url,
        "gdb_url": gdb_url,
        "created": not replaced,
        "replaced": replaced,
    }
    if auto_login:
        result["auto_login"] = auto_login
    return result


# ---------------------------------------------------------------------------
# instance_remove
# ---------------------------------------------------------------------------


async def instance_remove(name: str) -> dict[str, Any]:
    """Remove a custom instance profile. Destructive operation.

    Args:
        name: Profile name to remove.

    Returns:
        dict with removed (bool), name.
    """
    if not name or not name.strip():
        raise ToolError("Profile name is required.")
    name = name.strip().lower()

    profiles = load_profiles()
    if name not in profiles:
        available = ", ".join(sorted(profiles.keys())) or "none"
        raise ToolError(
            f"Profile '{name}' not found. Available custom profiles: {available}."
        )

    # Clean up auth data
    try:
        from mcp_eregistrations_common import auth_store

        auth_store.delete_instance(name)
    except Exception:
        pass

    del profiles[name]
    save_profiles(profiles)

    return {"removed": True, "name": name}


# ---------------------------------------------------------------------------
# Registration
# ---------------------------------------------------------------------------


def register_instance_tools(mcp: Any) -> None:
    """Register instance management tools with an MCP server.

    Args:
        mcp: The FastMCP server instance.
    """
    mcp.tool(annotations=READ)(instance_list)
    mcp.tool(annotations=WRITE)(instance_add)
    mcp.tool(annotations=DESTRUCTIVE)(instance_remove)
