"""Generate LangChain tools from SaaSModel and write agent/tools_generated.py."""

import os
import re
from pathlib import Path
from typing import Any

import httpx
from langchain_core.tools import StructuredTool

from foremet.ingest.model import Operation, SaaSModel


def _sanitize_tool_name(op: Operation) -> str:
    """Safe tool name from method + path (e.g. get_pets)."""
    raw = f"{op.method.lower()}_{op.path.strip('/').replace('/', '_')}"
    return re.sub(r"[^a-z0-9_]", "_", raw) or "api_call"


def _build_description(op: Operation) -> str:
    """One-line description for the tool."""
    parts = [op.summary or op.description or f"{op.method} {op.path}"]
    if op.parameters or op.request_body_schema:
        parts.append("Accepts parameters as keyword arguments.")
    return " ".join(parts)


def _run_operation(
    op: Operation,
    base_url: str,
    auth_value: str,
    **kwargs: Any,
) -> str:
    """Execute one API call. Path params and query from kwargs; body as kwargs['body'] or spread."""
    url = (base_url.rstrip("/") + op.path).format(**{p["name"]: kwargs.get(p["name"], "") for p in op.parameters if p.get("in") == "path"})
    params = {p["name"]: kwargs[p["name"]] for p in op.parameters if p.get("in") == "query" and p["name"] in kwargs}
    headers = {"Authorization": f"Bearer {auth_value}"} if auth_value else {}
    if op.request_body_schema and "body" in kwargs:
        import json
        body = kwargs["body"] if isinstance(kwargs["body"], (dict, list)) else json.loads(kwargs["body"])
    else:
        body = None
    resp = httpx.request(op.method, url, params=params or None, json=body, headers=headers or None, timeout=30.0)
    try:
        resp.raise_for_status()
    except httpx.HTTPStatusError as e:
        return f"Error: {e.response.status_code} {e.response.text}"
    try:
        return resp.json()
    except Exception:
        return resp.text


def _operation_to_tool(op: Operation, base_url: str, auth_env_var: str) -> StructuredTool:
    """Build one StructuredTool for an operation. Auth read from env at call time."""
    name = _sanitize_tool_name(op)
    description = _build_description(op)
    schema_dict: dict[str, tuple[type, Any]] = {}
    for p in op.parameters:
        schema_dict[p["name"]] = (str, None if not p.get("required") else ...)
    if op.request_body_schema and op.method in ("POST", "PUT", "PATCH"):
        schema_dict["body"] = (str | dict, None)  # type: ignore
    if not schema_dict:
        schema_dict["_dummy"] = (str, None)
    from pydantic import create_model
    args_model = create_model(f"Args_{name}", **{k: (v[0], v[1]) for k, v in schema_dict.items()})

    def _func(**kw: Any) -> str:
        auth_value = os.environ.get(auth_env_var) or ""
        return _run_operation(op, base_url, auth_value, **kw)

    return StructuredTool(
        name=name,
        description=description,
        args_schema=args_model,
        func=_func,
    )


def generate_tools(saas_model: SaaSModel, base_url: str, auth_env_var: str) -> list[StructuredTool]:
    """Build list of LangChain StructuredTools from SaaSModel. Reads auth from os.environ."""
    return [_operation_to_tool(op, base_url, auth_env_var) for op in saas_model.operations]


def _run_operation_from_dict(op_dict: dict[str, Any], base_url: str, auth_value: str, **kwargs: Any) -> str:
    """Run one operation from serialized dict (for generated file)."""
    method = op_dict["method"]
    path = op_dict["path"]
    params_list = op_dict.get("parameters") or []
    path_params = {p["name"]: kwargs.get(p["name"], "") for p in params_list if p.get("in") == "path"}
    url = (base_url.rstrip("/") + path).format(**path_params)
    query_params = {p["name"]: kwargs[p["name"]] for p in params_list if p.get("in") == "query" if p["name"] in kwargs}
    headers = {"Authorization": f"Bearer {auth_value}"} if auth_value else {}
    body = kwargs.get("body")
    if body is not None and isinstance(body, str):
        import json
        try:
            body = json.loads(body)
        except json.JSONDecodeError:
            pass
    resp = httpx.request(method, url, params=query_params or None, json=body, headers=headers or None, timeout=30.0)
    try:
        resp.raise_for_status()
    except httpx.HTTPStatusError as e:
        return f"Error: {e.response.status_code} {e.response.text}"
    try:
        return resp.json()
    except Exception:
        return resp.text


def build_tools_from_operations(
    operations: list[dict[str, Any]],
    base_url: str,
    auth_env_var: str,
) -> list[StructuredTool]:
    """Build tools from list of operation dicts (used by generated tools_generated.py)."""
    tools = []
    for op_dict in operations:
        name = op_dict.get("name") or f"{op_dict['method']}_{op_dict['path'].replace('/', '_')}"
        name = re.sub(r"[^a-z0-9_]", "_", name.lower())
        description = op_dict.get("description") or f"{op_dict['method']} {op_dict['path']}"
        params_list = op_dict.get("parameters") or []
        schema_dict: dict[str, tuple[type, Any]] = {}
        for p in params_list:
            schema_dict[p["name"]] = (str, None if not p.get("required") else ...)
        if op_dict.get("request_body_schema") and op_dict["method"] in ("POST", "PUT", "PATCH"):
            schema_dict["body"] = (str | dict, None)  # type: ignore
        if not schema_dict:
            schema_dict["_dummy"] = (str, None)
        from pydantic import create_model
        args_model = create_model(f"Args_{name}", **{k: (v[0], v[1]) for k, v in schema_dict.items()})
        op = op_dict
        tools.append(StructuredTool(
            name=name,
            description=description,
            args_schema=args_model,
            func=lambda **kw, _op=op, _base=base_url, _ev=auth_env_var: _run_operation_from_dict(
                _op, _base, os.environ.get(_ev) or "", **kw
            ),
        ))
    return tools


def write_generated_tools(output_dir: Path, saas_model: SaaSModel) -> Path:
    """Write agent/tools_generated.py that exports get_tools(base_url, auth_env_var)."""
    output_dir = Path(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)
    operations_data = []
    for op in saas_model.operations:
        operations_data.append({
            "name": _sanitize_tool_name(op),
            "method": op.method,
            "path": op.path,
            "description": _build_description(op),
            "parameters": op.parameters,
            "request_body_schema": op.request_body_schema,
        })
    content = f'''# Generated by foremet - do not edit by hand.
import os
from pathlib import Path

# Import from foremet so agent dir can use get_tools without re-parsing OpenAPI.
from foremet.generate.tools import build_tools_from_operations

OPERATIONS = {operations_data!r}


def get_tools(base_url: str, auth_env_var: str):
    """Return list of LangChain tools for the SaaS API. Reads token from os.environ[auth_env_var]."""
    return build_tools_from_operations(OPERATIONS, base_url, auth_env_var)
'''
    path = output_dir / "tools_generated.py"
    path.write_text(content)
    return path
