"""Functions mixin for MCGhidra.

Provides tools for function analysis, decompilation, and manipulation.
"""

from typing import Any, Dict, List, Optional
from urllib.parse import quote

from fastmcp import Context
from fastmcp.contrib.mcp_mixin import mcp_resource, mcp_tool

from ..config import get_config
from .base import MCGhidraMixinBase


class FunctionsMixin(MCGhidraMixinBase):
    """Mixin for function operations.

    Provides tools for:
    - Listing and searching functions
    - Decompiling functions
    - Disassembling functions
    - Renaming functions
    - Setting function signatures
    - Managing function variables
    """

    @mcp_tool()
    def functions_list(
        self,
        name_contains: Optional[str] = None,
        name_regex: Optional[str] = None,
        address: Optional[str] = None,
        port: Optional[int] = None,
        page_size: int = 50,
        grep: Optional[str] = None,
        grep_ignorecase: bool = True,
        return_all: bool = False,
        fields: Optional[List[str]] = None,
        ctx: Optional[Context] = None,
    ) -> Dict[str, Any]:
        """List functions with cursor-based pagination and server-side filtering.

        Args:
            name_contains: Server-side substring filter on function name (faster than grep for large binaries)
            name_regex: Server-side regex filter on function name
            address: Filter by exact function address (hex)
            port: Ghidra instance port (optional)
            page_size: Functions per page (default: 50, max: 500)
            grep: Client-side regex pattern to filter function names
            grep_ignorecase: Case-insensitive grep (default: True)
            return_all: Return all functions without pagination
            fields: Field names to keep (e.g. ['name', 'address']). Reduces response size.
            ctx: FastMCP context (auto-injected)

        Returns:
            Paginated list of functions
        """
        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return {"success": False, "error": {"code": "NO_INSTANCE", "message": str(e)}}

        config = get_config()
        params = {"limit": 10000}
        if name_contains:
            params["name_contains"] = name_contains
        if name_regex:
            params["name_matches_regex"] = name_regex
        if address:
            params["addr"] = address

        response = self.safe_get(port, "functions", params)
        simplified = self.simplify_response(response)

        if not simplified.get("success", True):
            return simplified

        functions = simplified.get("result", [])
        if not isinstance(functions, list):
            functions = []

        query_params = {
            "tool": "functions_list",
            "port": port,
            "name_contains": name_contains,
            "name_regex": name_regex,
            "address": address,
            "grep": grep,
        }
        session_id = self._get_session_id(ctx)

        return self.filtered_paginate(
            data=functions,
            query_params=query_params,
            tool_name="functions_list",
            session_id=session_id,
            page_size=min(page_size, config.max_page_size),
            grep=grep,
            grep_ignorecase=grep_ignorecase,
            return_all=return_all,
            fields=fields,
        )

    @mcp_tool()
    def functions_get(
        self,
        name: Optional[str] = None,
        address: Optional[str] = None,
        port: Optional[int] = None,
    ) -> Dict[str, Any]:
        """Get detailed information about a function.

        Args:
            name: Function name (mutually exclusive with address)
            address: Function address in hex format
            port: Ghidra instance port (optional)

        Returns:
            Detailed function information
        """
        if not name and not address:
            return {
                "success": False,
                "error": {
                    "code": "MISSING_PARAMETER",
                    "message": "Either name or address parameter is required",
                },
            }

        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return {"success": False, "error": {"code": "NO_INSTANCE", "message": str(e)}}

        if address:
            endpoint = f"functions/{address}"
        else:
            endpoint = f"functions/by-name/{quote(name)}"

        response = self.safe_get(port, endpoint)
        return self.simplify_response(response)

    @mcp_tool()
    def functions_decompile(
        self,
        name: Optional[str] = None,
        address: Optional[str] = None,
        syntax_tree: bool = False,
        style: str = "normalize",
        port: Optional[int] = None,
        page_size: int = 50,
        grep: Optional[str] = None,
        grep_ignorecase: bool = True,
        return_all: bool = False,
        fields: Optional[List[str]] = None,
        ctx: Optional[Context] = None,
    ) -> Dict[str, Any]:
        """Get decompiled code for a function with line pagination.

        Args:
            name: Function name (mutually exclusive with address)
            address: Function address in hex format
            syntax_tree: Include syntax tree (default: False)
            style: Decompiler style (default: "normalize")
            port: Ghidra instance port (optional)
            page_size: Lines per page (default: 50, max: 500)
            grep: Regex pattern to filter lines
            grep_ignorecase: Case-insensitive grep (default: True)
            return_all: Return all lines without pagination
            fields: Field names to keep (for structured results)
            ctx: FastMCP context (auto-injected)

        Returns:
            Decompiled code with pagination
        """
        if not name and not address:
            return {
                "success": False,
                "error": {
                    "code": "MISSING_PARAMETER",
                    "message": "Either name or address parameter is required",
                },
            }

        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return {"success": False, "error": {"code": "NO_INSTANCE", "message": str(e)}}

        config = get_config()

        if address:
            endpoint = f"functions/{address}/decompile"
        else:
            endpoint = f"functions/by-name/{quote(name)}/decompile"

        params = {"syntaxTree": str(syntax_tree).lower(), "style": style}
        response = self.safe_get(port, endpoint, params)
        simplified = self.simplify_response(response)

        if not simplified.get("success", True):
            return simplified

        result = simplified.get("result", {})
        decompiled = result.get("decompiled_text", result.get("ccode", ""))

        if not decompiled:
            return simplified

        # Split into lines for pagination
        lines = decompiled.split("\n")

        query_params = {
            "tool": "functions_decompile",
            "port": port,
            "name": name,
            "address": address,
            "grep": grep,
        }
        session_id = self._get_session_id(ctx)

        paginated = self.filtered_paginate(
            data=lines,
            query_params=query_params,
            tool_name="functions_decompile",
            session_id=session_id,
            page_size=min(page_size, config.max_page_size),
            grep=grep,
            grep_ignorecase=grep_ignorecase,
            return_all=return_all,
            fields=fields,
        )

        # Convert lines back to text in result (skip if guarded)
        if paginated.get("success") and not paginated.get("guarded"):
            paginated["result"] = "\n".join(paginated.get("result", []))
            paginated["function_name"] = result.get("name", name or address)

        return paginated

    @mcp_tool()
    def functions_disassemble(
        self,
        name: Optional[str] = None,
        address: Optional[str] = None,
        port: Optional[int] = None,
        page_size: int = 50,
        grep: Optional[str] = None,
        grep_ignorecase: bool = True,
        return_all: bool = False,
        fields: Optional[List[str]] = None,
        ctx: Optional[Context] = None,
    ) -> Dict[str, Any]:
        """Get disassembly for a function with instruction pagination.

        Args:
            name: Function name (mutually exclusive with address)
            address: Function address in hex format
            port: Ghidra instance port (optional)
            page_size: Instructions per page (default: 50, max: 500)
            grep: Regex pattern to filter instructions
            grep_ignorecase: Case-insensitive grep (default: True)
            return_all: Return all instructions without pagination
            fields: Field names to keep (for structured results)
            ctx: FastMCP context (auto-injected)

        Returns:
            Disassembly with pagination
        """
        if not name and not address:
            return {
                "success": False,
                "error": {
                    "code": "MISSING_PARAMETER",
                    "message": "Either name or address parameter is required",
                },
            }

        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return {"success": False, "error": {"code": "NO_INSTANCE", "message": str(e)}}

        config = get_config()

        if address:
            endpoint = f"functions/{address}/disassembly"
        else:
            endpoint = f"functions/by-name/{quote(name)}/disassembly"

        response = self.safe_get(port, endpoint)
        simplified = self.simplify_response(response)

        if not simplified.get("success", True):
            return simplified

        result = simplified.get("result", {})
        disasm_text = result.get("disassembly_text", "")

        if not disasm_text:
            return simplified

        # Split into lines for pagination
        lines = [line for line in disasm_text.split("\n") if line.strip()]

        query_params = {
            "tool": "functions_disassemble",
            "port": port,
            "name": name,
            "address": address,
            "grep": grep,
        }
        session_id = self._get_session_id(ctx)

        paginated = self.filtered_paginate(
            data=lines,
            query_params=query_params,
            tool_name="functions_disassemble",
            session_id=session_id,
            page_size=min(page_size, config.max_page_size),
            grep=grep,
            grep_ignorecase=grep_ignorecase,
            return_all=return_all,
            fields=fields,
        )

        # Convert lines back to text (skip if guarded)
        if paginated.get("success") and not paginated.get("guarded"):
            paginated["result"] = "\n".join(paginated.get("result", []))
            paginated["function_name"] = result.get("name", name or address)

        return paginated

    @mcp_tool()
    def functions_rename(
        self,
        old_name: Optional[str] = None,
        address: Optional[str] = None,
        new_name: str = "",
        port: Optional[int] = None,
    ) -> Dict[str, Any]:
        """Rename a function.

        Args:
            old_name: Current function name
            address: Function address in hex format
            new_name: New name for the function
            port: Ghidra instance port (optional)

        Returns:
            Operation result
        """
        if not new_name:
            return {
                "success": False,
                "error": {
                    "code": "MISSING_PARAMETER",
                    "message": "new_name parameter is required",
                },
            }

        if not old_name and not address:
            return {
                "success": False,
                "error": {
                    "code": "MISSING_PARAMETER",
                    "message": "Either old_name or address parameter is required",
                },
            }

        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return {"success": False, "error": {"code": "NO_INSTANCE", "message": str(e)}}

        if address:
            endpoint = f"functions/{address}"
        else:
            endpoint = f"functions/by-name/{quote(old_name)}"

        payload = {"name": new_name}
        response = self.safe_patch(port, endpoint, payload)
        return self.simplify_response(response)

    @mcp_tool()
    def functions_create(self, address: str, port: Optional[int] = None) -> Dict[str, Any]:
        """Create a function at the specified address.

        Args:
            address: Memory address in hex format
            port: Ghidra instance port (optional)

        Returns:
            Created function information
        """
        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return {"success": False, "error": {"code": "NO_INSTANCE", "message": str(e)}}

        payload = {"address": address}
        response = self.safe_post(port, "functions", payload)
        return self.simplify_response(response)

    @mcp_tool()
    def functions_set_signature(
        self,
        name: Optional[str] = None,
        address: Optional[str] = None,
        signature: str = "",
        port: Optional[int] = None,
    ) -> Dict[str, Any]:
        """Set the signature/prototype of a function.

        Args:
            name: Function name
            address: Function address in hex format
            signature: New function signature (e.g., "int foo(char* arg1, int arg2)")
            port: Ghidra instance port (optional)

        Returns:
            Operation result
        """
        if not signature:
            return {
                "success": False,
                "error": {
                    "code": "MISSING_PARAMETER",
                    "message": "signature parameter is required",
                },
            }

        if not name and not address:
            return {
                "success": False,
                "error": {
                    "code": "MISSING_PARAMETER",
                    "message": "Either name or address parameter is required",
                },
            }

        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return {"success": False, "error": {"code": "NO_INSTANCE", "message": str(e)}}

        if address:
            endpoint = f"functions/{address}/signature"
        else:
            endpoint = f"functions/by-name/{quote(name)}/signature"

        payload = {"signature": signature}
        response = self.safe_put(port, endpoint, payload)
        return self.simplify_response(response)

    # Resources

    @mcp_resource(uri="ghidra://instance/{port}/functions")
    def resource_functions_list(self, port: Optional[int] = None) -> Dict[str, Any]:
        """MCP Resource: List functions (capped).

        Args:
            port: Ghidra instance port

        Returns:
            List of functions (capped at 1000)
        """
        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return {"error": str(e)}

        config = get_config()
        cap = config.resource_caps.get("functions", 1000)

        response = self.safe_get(port, "functions", {"limit": cap})
        simplified = self.simplify_response(response)

        if not simplified.get("success", True):
            return simplified

        functions = simplified.get("result", [])
        if not isinstance(functions, list):
            functions = []

        return {
            "functions": functions[:cap],
            "count": len(functions),
            "capped_at": cap if len(functions) >= cap else None,
            "_hint": "Use functions_list() tool for full pagination" if len(functions) >= cap else None,
        }

    @mcp_resource(uri="ghidra://instance/{port}/function/decompile/address/{address}")
    def resource_decompiled_by_address(
        self, port: Optional[int] = None, address: Optional[str] = None
    ) -> str:
        """MCP Resource: Get decompiled code by address.

        Args:
            port: Ghidra instance port
            address: Function address

        Returns:
            Decompiled code as text
        """
        if not address:
            return "Error: address is required"

        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return f"Error: {e}"

        response = self.safe_get(port, f"functions/{address}/decompile")
        simplified = self.simplify_response(response)

        if not simplified.get("success", True):
            error = simplified.get("error", {})
            return f"Error: {error.get('message', 'Unknown error')}"

        result = simplified.get("result", {})
        return result.get("decompiled_text", result.get("ccode", "No decompiled code available"))

    @mcp_resource(uri="ghidra://instance/{port}/function/decompile/name/{name}")
    def resource_decompiled_by_name(
        self, port: Optional[int] = None, name: Optional[str] = None
    ) -> str:
        """MCP Resource: Get decompiled code by function name.

        Args:
            port: Ghidra instance port
            name: Function name

        Returns:
            Decompiled code as text
        """
        if not name:
            return "Error: name is required"

        try:
            port = self.get_instance_port(port)
        except ValueError as e:
            return f"Error: {e}"

        response = self.safe_get(port, f"functions/by-name/{quote(name)}/decompile")
        simplified = self.simplify_response(response)

        if not simplified.get("success", True):
            error = simplified.get("error", {})
            return f"Error: {error.get('message', 'Unknown error')}"

        result = simplified.get("result", {})
        return result.get("decompiled_text", result.get("ccode", "No decompiled code available"))
