"""
Strict‑mode tool schema: a JSON Schema object with the following structure:

- type: "object"
- properties: a mapping of field names to schemas. Each property must declare:
    - type: one of "string", "integer", "number", "boolean", "array", or "object"
    - optional constraints (enum, minimum/maximum, pattern, items for arrays, etc.)
    - nested objects must themselves define type, properties, required, and
      additionalProperties (usually false for strictness)
- required: a list containing every key defined in properties (strict mode requires
  all properties to be listed here, even if defaults are provided)
- additionalProperties: false (disallows undeclared fields)

Defaults do not make fields optional. Arrays must specify an item schema. Nested
objects should follow the same strict pattern for predictability.


PROBE_DEPENDENCIES: Dict[str, List[str]] = {
    "tcp_probe": [],
    "banner_probe": [],
    "tls_probe": [],
    "cert_chain_probe": ["tls_probe"],  # needs tls_probe.certificate_der
    "service_probe": ["tcp_probe", "banner_probe"],  # tcp_classification + banner_text
    "http_probe": ["banner_probe"],  # raw_bytes + decoded_text
    "tls_cipher_sweep": [],
}
"""

from typing import List, Dict, Any, Union, Callable
from .register_parser import find_firewall_rules_by_port, find_firewall_rules_by_ports
from .tcp_probe import tcp_probe, tcp_probes
from .banner_probe import banner_probe
from .tls_probe import tls_probe
from .http_probe import http_probe
from .service_probe import service_probe
from .cert_chain_probe import cert_chain_probe
from .tls_cipher_sweep import tls_cipher_sweep
from .authentic import authentic, authentics
from openai.types.responses.function_tool_param import FunctionToolParam
import subprocess

Tool_Result = Dict[str, Any]
Tool_Args = Dict[str, Any]



def dispatch_tool(tool_name: str, args: Tool_Args) -> Tool_Result:
    """Dispatch a tool call to the appropriate function."""

    run_list: dict[str, Union[list[str], Callable]] = {
        # run_list: dict[str, Union[list[str], Callable[..., Any]]] = {
        "tasklist": ["tasklist", "/svc"],
        "netstat": ["netstat", "-ano"],
        "firewall_settings": ["netsh", "advfirewall", "show", "allprofiles"],
        "exist_check": ["pwsh", "-Command", f'Test-Path "{ args.get('path', []) }"'],
        "get_process_name": [
            "powershell",
            "-Command",
            f"Get-CimInstance Win32_Process -Filter 'ProcessId={args.get('PID',[])}' | Select ProcessId,Name",
        ],
        # "authentic": [
        #     "pwsh",
        #     "-Command",
        #     f'Get-AuthenticodeSignature "{args.get('path', [])}"',
        # ],
        "file_version": [
            "powershell",
            "-Command",
            f"(Get-Item \"{ args.get('path', []) }\").VersionInfo.FileVersion",
        ],
        "find_firewall_rules_by_port": find_firewall_rules_by_port,
        "find_firewall_rules_by_ports": find_firewall_rules_by_ports,
        "tcp_probe": tcp_probe,
        "tcp_probes": tcp_probes,
        "banner_probe": banner_probe,
        "tls_probe": tls_probe,
        "http_probe": http_probe,
        "service_probe": service_probe,
        "cert_chain_probe": cert_chain_probe,
        "tls_cipher_sweep": tls_cipher_sweep,
        "authentic": authentic,
        "authentics": authentics,
    }

    try:
        if callable(run_list[tool_name]):
            return run_list[tool_name](**args)  # type: ignore

        result = run_command(run_list[tool_name])  # type: ignore
        return result

    except Exception as e:
        return {"error": f"Tool execution failed: {e}"}


# ...........................
# TOOL DEFINITIONS for agent
# ...........................
TOOLS_DEFS: List[FunctionToolParam] = [
    {
        "type": "function",
        "strict": True,
        "name": "tcp_probe",
        "description": "Attempt a TCP connection to a local address. No banner read. No TLS.",
        "parameters": {
            "type": "object",
            "properties": {
                "local_address_mode": {
                    "type": "string",
                    "enum": ["ipv4_loopback", "ipv6_loopback", "hostname_localhost", "primary_ipv4"],
                },
                "port": {"type": "integer"},
                "timeout": {"type": "number"},
            },
            "required": ["local_address_mode", "port", "timeout"],
            "additionalProperties": False,
        },
    },
    {
        "type": "function",
        "strict": True,
        "name": "tcp_probes",
        "description": "For multiple ports, Attempt a TCP connection to a local address. No banner read. No TLS.",
        "parameters": {
            "type": "object",
            "properties": {
                "local_address_mode": {
                    "type": "string",
                    "enum": ["ipv4_loopback", "ipv6_loopback", "hostname_localhost", "primary_ipv4"],
                },
                "ports": {"type": "array", "items": {"type": "integer"}, "description": "List of ports to probe."},
                "timeout": {"type": "number"},
            },
            "required": ["local_address_mode", "ports", "timeout"],
            "additionalProperties": False,
        },
    },
    {
        "type": "function",
        "strict": True,
        "name": "banner_probe",
        "description": "Connect to a local TCP service. Passive read-only fingerprint. No bytes sent.",
        "parameters": {
            "type": "object",
            "properties": {
                "local_address_mode": {
                    "type": "string",
                    "enum": ["ipv4_loopback", "ipv6_loopback", "hostname_localhost", "primary_ipv4"],
                },
                "port": {"type": "integer"},
                "timeout": {"type": "number"},
            },
            "required": ["local_address_mode", "port", "timeout"],
            "additionalProperties": False,
        },
    },
    {
        "type": "function",
        "strict": True,
        "name": "tls_probe",
        "description": "Perform a minimal TLS handshake to a local service and extract negotiated parameters. This is fast—negotiation metadata only.",
        "parameters": {
            "type": "object",
            "properties": {
                "local_address_mode": {
                    "type": "string",
                    "enum": ["ipv4_loopback", "ipv6_loopback", "hostname_localhost", "primary_ipv4"],
                },
                "port": {"type": "integer"},
                "timeout": {"type": "number"},
            },
            "required": [
                "local_address_mode",
                "port",
                "timeout",
            ],
            "additionalProperties": False,
        },
    },
    {
        "type": "function",
        "strict": True,
        "name": "cert_chain_probe",
        "description": "Retrieve the certificate chain from a local TLS service without validation. This is a slow, full chain extraction",
        "parameters": {
            "type": "object",
            "properties": {
                "local_address_mode": {
                    "type": "string",
                    "enum": ["ipv4_loopback", "ipv6_loopback", "hostname_localhost", "primary_ipv4"],
                },
                "port": {"type": "integer"},
            },
            "required": [
                "local_address_mode",
                "port",
            ],
            "additionalProperties": False,
        },
    },
    {
        "type": "function",
        "strict": True,
        "name": "tls_cipher_sweep",
        "description": "Enumerate supported TLS versions and cipher suites by forcing version+cipher combinations.",
        "parameters": {
            "type": "object",
            "properties": {
                "local_address_mode": {
                    "type": "string",
                    "enum": ["ipv4_loopback", "ipv6_loopback", "hostname_localhost", "primary_ipv4"],
                },
                "port": {"type": "integer"},
                "timeout": {"type": "number"},
                "tls_versions": {"type": "array", "items": {"type": "string"}},
                "cipher_list": {"type": "array", "items": {"type": "string"}},
            },
            "required": ["local_address_mode", "port", "timeout", "tls_versions", "cipher_list"],
            "additionalProperties": False,
        },
    },
    {
        "type": "function",
        "strict": True,
        "name": "service_probe",
        "description": "Classify a local service using TCP reachability and banner inspection. Returns a summary plus embedded tcp_probe() and banner_probe() results.",
        "parameters": {
            "type": "object",
            "properties": {
                "local_address_mode": {
                    "type": "string",
                    "enum": ["ipv4_loopback", "ipv6_loopback", "hostname_localhost", "primary_ipv4"],
                },
                "port": {"type": "integer"},
            },
            "required": ["local_address_mode", "port"],
            "additionalProperties": False,
        },
    },
    {
        "type": "function",
        "strict": True,
        "name": "http_probe",
        "description": "Perform an HTTP probe. Active L7 request. Bytes sent. Runs banner_probe() internally and parses HTTP response bytes.",
        "parameters": {
            "type": "object",
            "properties": {
                "local_address_mode": {
                    "type": "string",
                    "enum": ["ipv4_loopback", "ipv6_loopback", "hostname_localhost", "primary_ipv4"],
                },
                "port": {"type": "integer"},
            },
            "required": ["local_address_mode", "port"],
            "additionalProperties": False,
        },
    },
    {
        "type": "function",
        "strict": True,
        "name": "authentic",
        "description": """Determine if a process is authentic by validating its signature, executable path, and hosted services.""",
        "parameters": {
            "type": "object",
            "properties": {
                "pid": {"type": "integer"},
            },
            "required": ["pid"],
            "additionalProperties": False,
        },
    },
    {
        "type": "function",
        "strict": True,
        "name": "authentics",
        "description": """Determine if a process is authentic by validating its signature, executable path, and hosted services.""",
        "parameters": {
            "type": "object",
            "properties": {
                "pids": {"type": "array", "items": {"type": "integer"}, "description": "List of process IDs to authenticate."},
            },
            "required": ["pids"],
            "additionalProperties": False,
        },
    },
    {
        "name": "tasklist",
        "type": "function",
        "description": "Show each running process, with their Image Name, PID, and the services hosted inside it.",
        "parameters": {
            "type": "object",
            "properties": {},
        },
        "strict": False,
    },
    {
        "name": "netstat",
        "type": "function",
        "description": "List all active network connections and listening ports with protocol, addresses, state, and process IDs, but not the executable name.",
        "parameters": {
            "type": "object",
            "properties": {},
        },
        "strict": False,
    },
    {
        "name": "get_process_name",
        "type": "function",
        "description": "fetch the name of a process.",
        "parameters": {
            "type": "object",
            "properties": {"PID": {"type": "integer"}},
            "required": ["PID"],
        },
        "strict": False,
    },
    # {
    #     "name": "authentic",
    #     "type": "function",
    #     "description": "Confirm if the binary file at the given path is signed by Microsoft or a trusted vendor.",
    #     "parameters": {
    #         "type": "object",
    #         "properties": {"path": {"type": "string"}},
    #         "required": ["path"],
    #     },
    #     "strict": False,
    # },
    {
        "name": "find_firewall_rules_by_port",
        "type": "function",
        "description": "list firewall rules that apply to the given port",
        "parameters": {
            "type": "object",
            "properties": {"port": {"type": "integer"}},
            "required": ["port"],
        },
        "strict": False,
    },
    {
        "name": "find_firewall_rules_by_ports",
        "type": "function",
        "description": "Firewall rules for multiple ports. The output is organized as: {port: [rules]}",
        "parameters": {
            "type": "object",
            "properties": {"ports": {"type": "array", "items": {"type": "integer"}}},
            "required": ["ports"],
        },
        "strict": False,
    },
    {
        "name": "firewall_settings",
        "type": "function",
        "description": "Summarize the global firewall profile settings (Domain, Private, Public).",
        "parameters": {
            "type": "object",
            "properties": {},
        },
        "strict": False,
    },
    {
        "name": "exist_check",
        "type": "function",
        "description": "Check if the file at path exists.",
        "parameters": {
            "type": "object",
            "properties": {"path": {"type": "string"}},
            "required": ["path"],
        },
        "strict": False,
    },
    {
        "name": "file_version",
        "type": "function",
        "description": "Return the file version formatted as Major.Minor.Build.Revision",
        "parameters": {
            "type": "object",
            "properties": {"path": {"type": "string"}},
            "required": ["path"],
        },
        "strict": False,
    },
]


def run_command(func_call: List[str], timeout: int = 4) -> Tool_Result:
    """
    This is a wrapper for subprocess.run().
      - It provides error handling and messaging.
      - It structures the result in a dict,
    """

    try:
        print(f"[DEVELOP: Run_Command]  {' '.join(func_call)}")
        result = subprocess.run(
            func_call, capture_output=True, text=True, timeout=timeout
        )
        if result.returncode:
            print("[DEVELOP] subrocess result:", result)
            output = {
                "success": result.returncode == 0,
                "return code": result.returncode,
                "output": result.stderr.strip(),
            }
        else:
            output = {
                "success": result.returncode == 0,
                "output": result.stdout.strip(),
            }
        return output

    except Exception as e:
        return {"error": str(e)}
