"""Instance management mixin for MCGhidra.

Provides tools for discovering, registering, and managing Ghidra instances.
"""

import time
from typing import Any, Dict, Optional

from fastmcp.contrib.mcp_mixin import mcp_resource, mcp_tool

from ..config import get_config
from .base import MCGhidraMixinBase


class InstancesMixin(MCGhidraMixinBase):
    """Mixin for Ghidra instance management.

    Provides tools for:
    - Discovering Ghidra instances
    - Registering/unregistering instances
    - Setting current working instance
    - Listing available instances
    """

    def _discover_instances(
        self,
        port_range: range,
        host: Optional[str] = None,
        timeout: float = 0.5,
    ) -> Dict[int, Dict[str, Any]]:
        """Discover Ghidra instances by scanning ports.

        Args:
            port_range: Range of ports to scan
            host: Host to scan (defaults to config)
            timeout: Connection timeout per port

        Returns:
            Dict of discovered instances
        """
        import requests
        config = get_config()
        if host is None:
            host = config.ghidra_host

        discovered = {}

        for port in port_range:
            try:
                url = f"http://{host}:{port}/"
                response = requests.get(url, timeout=timeout)
                if response.ok:
                    data = response.json()
                    # Verify it's a Ghidra HATEOAS API
                    if "_links" in data or "api_version" in data:
                        instance_info = {
                            "url": f"http://{host}:{port}",
                            "project": data.get("project", ""),
                            "file": data.get("file", ""),
                            "api_version": data.get("api_version"),
                            "discovered_at": time.time(),
                        }
                        discovered[port] = instance_info

                        # Also register it
                        with self._instances_lock:
                            self._instances[port] = instance_info

            except (requests.exceptions.Timeout, requests.exceptions.ConnectionError):
                pass
            except Exception:
                pass

        return discovered

    @mcp_tool()
    def instances_list(self) -> Dict[str, Any]:
        """List all active Ghidra instances.

        This is the primary tool for working with instances. It automatically
        discovers new instances on the default host before listing.

        Returns:
            Dict containing 'instances' list with all available Ghidra instances
        """
        config = get_config()
        # Auto-discover before listing
        self._discover_instances(config.quick_discovery_range, timeout=0.5)

        with self._instances_lock:
            return {
                "instances": [
                    {
                        "port": port,
                        "url": info["url"],
                        "project": info.get("project", ""),
                        "file": info.get("file", ""),
                    }
                    for port, info in self._instances.items()
                ]
            }

    @mcp_tool()
    def instances_discover(self, host: Optional[str] = None) -> Dict[str, Any]:
        """Discover Ghidra instances on a specific host.

        Use this ONLY when you need to discover instances on a different host.
        For normal usage, just use instances_list() which auto-discovers.

        Args:
            host: Host to scan for Ghidra instances (default: configured host)

        Returns:
            Dict containing 'instances' list with all available instances
        """
        config = get_config()
        self._discover_instances(config.full_discovery_range, host=host, timeout=0.5)

        with self._instances_lock:
            return {
                "instances": [
                    {
                        "port": port,
                        "url": info["url"],
                        "project": info.get("project", ""),
                        "file": info.get("file", ""),
                    }
                    for port, info in self._instances.items()
                ]
            }

    @mcp_tool()
    def instances_register(self, port: int, url: Optional[str] = None) -> str:
        """Register a new Ghidra instance.

        Args:
            port: Port number of the Ghidra instance
            url: Optional URL if different from default

        Returns:
            Confirmation message or error
        """
        return self.register_instance(port, url)

    @mcp_tool()
    def instances_unregister(self, port: int) -> str:
        """Unregister a Ghidra instance.

        Args:
            port: Port number of the instance to unregister

        Returns:
            Confirmation message
        """
        return self.unregister_instance(port)

    @mcp_tool()
    def instances_use(self, port: int) -> str:
        """Set the current working Ghidra instance.

        All subsequent commands will use this instance by default.

        Args:
            port: Port number of the instance to use

        Returns:
            Confirmation message with instance details
        """
        # Register lazily without blocking HTTP calls.
        # If the instance is unknown, create a stub entry — the first
        # actual tool call (functions_list, etc.) will validate the
        # connection and fail fast with a clear error if unreachable.
        with self._instances_lock:
            if port not in self._instances:
                config = get_config()
                self._instances[port] = {
                    "url": f"http://{config.ghidra_host}:{port}",
                    "project": "",
                    "file": "",
                    "registered_at": time.time(),
                    "lazy": True,
                }

        self.set_current_port(port)

        info = self.get_instance_info(port)
        if info:
            return (
                f"Now using Ghidra instance on port {port}\n"
                f"Project: {info.get('project', 'N/A')}\n"
                f"File: {info.get('file', 'N/A')}"
            )
        return f"Now using Ghidra instance on port {port}"

    @mcp_tool()
    def instances_current(self) -> Dict[str, Any]:
        """Get information about the current working instance.

        Returns:
            Dict with current instance information or error message
        """
        port = self.get_current_port()
        if port is None:
            return {
                "error": "No current instance set. Use instances_use(port) first.",
                "available_instances": list(self._instances.keys()),
            }

        info = self.get_instance_info(port)
        if info:
            return {
                "port": port,
                "url": info["url"],
                "project": info.get("project", ""),
                "file": info.get("file", ""),
            }

        return {"port": port, "status": "registered but no details available"}

    @mcp_tool()
    def program_info(self, port: Optional[int] = None) -> Dict[str, Any]:
        """Get full program metadata (architecture, language, compiler, image base, memory size).

        Args:
            port: Ghidra instance port (optional)

        Returns:
            Program metadata including architecture, language, compiler spec,
            image base address, and total memory size
        """
        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return {"success": False, "error": {"code": "NO_INSTANCE", "message": str(e)}}

        response = self.safe_get(port, "program")
        return self.simplify_response(response)

    @mcp_resource(uri="ghidra://instances")
    def resource_instances_list(self) -> Dict[str, Any]:
        """MCP Resource: List all active Ghidra instances.

        Returns a lightweight enumeration of instances for quick reference.
        """
        config = get_config()
        # Auto-discover before listing
        self._discover_instances(config.quick_discovery_range, timeout=0.3)

        with self._instances_lock:
            instances = [
                {
                    "port": port,
                    "project": info.get("project", ""),
                    "file": info.get("file", ""),
                }
                for port, info in self._instances.items()
            ]

        return {
            "instances": instances,
            "count": len(instances),
            "current_port": self.get_current_port(),
        }

    @mcp_resource(uri="ghidra://instance/{port}")
    def resource_instance_info(self, port: int) -> Dict[str, Any]:
        """MCP Resource: Get detailed information about a Ghidra instance.

        Args:
            port: Port number of the instance

        Returns:
            Instance information including program details
        """
        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return {"error": str(e)}

        response = self.safe_get(port, "")
        if not response.get("success", True):
            return response

        return self.simplify_response(response)

    @mcp_resource(uri="ghidra://instance/{port}/summary")
    def resource_program_summary(self, port: Optional[int] = None) -> Dict[str, Any]:
        """MCP Resource: Get a summary of the program loaded in a Ghidra instance.

        Args:
            port: Ghidra instance port (optional, uses current if not specified)

        Returns:
            Program summary with statistics
        """
        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return {"error": str(e)}

        # Get program info
        response = self.safe_get(port, "")
        if not response.get("success", True):
            return response

        simplified = self.simplify_response(response)

        # Get function count
        funcs_response = self.safe_get(port, "functions", {"limit": 1})
        func_count = funcs_response.get("size", 0) if funcs_response.get("success", True) else 0

        # Get string count
        strings_response = self.safe_get(port, "data/strings", {"limit": 1})
        string_count = strings_response.get("size", 0) if strings_response.get("success", True) else 0

        return {
            "program_name": simplified.get("result", {}).get("program_name", simplified.get("file", "")),
            "language": simplified.get("result", {}).get("language", ""),
            "processor": simplified.get("result", {}).get("processor", ""),
            "format": simplified.get("result", {}).get("format", ""),
            "function_count": func_count,
            "string_count": string_count,
            "port": port,
        }

    @mcp_resource(uri="ghidra://instance/{port}/program")
    def resource_program_info(self, port: Optional[int] = None) -> Dict[str, Any]:
        """MCP Resource: Get program metadata for a Ghidra instance.

        Args:
            port: Ghidra instance port

        Returns:
            Program metadata (architecture, language, compiler, image base)
        """
        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return {"error": str(e)}

        response = self.safe_get(port, "program")
        return self.simplify_response(response)
