# retoor <retoor@molodetz.nl>

from __future__ import annotations

import logging
import xmlrpc.client
from typing import Any

from devplacepy import stealth
from devplacepy.config import INTERNAL_BASE_URL
from .registry import Method

logger = logging.getLogger("xmlrpc.bridge")

FAULT_INVALID_PARAMS = -32602
FAULT_MISSING_PARAM = -32001
FAULT_TRANSPORT = -32300

WRITE_METHODS = {"POST", "PUT", "DELETE", "PATCH"}
TIMEOUT_SECONDS = 60.0


def _coerce_value(value: Any) -> str:
    if isinstance(value, bool):
        return "1" if value else "0"
    return str(value)


def _build_path(method: Method, params: dict[str, Any]) -> str:
    path = method.path
    for param in method.path_params:
        if param.name not in params:
            raise xmlrpc.client.Fault(
                FAULT_MISSING_PARAM,
                f"Missing required path parameter '{param.name}' for {method.name}.",
            )
        path = path.replace("{" + param.name + "}", _coerce_value(params[param.name]))
    return path


def _split_params(
    method: Method, params: dict[str, Any]
) -> tuple[dict[str, str], dict[str, str]]:
    declared = {p.name: p for p in method.params}
    path_names = {p.name for p in method.path_params}
    query: dict[str, str] = {}
    form: dict[str, str] = {}
    for key, value in params.items():
        if key in path_names:
            continue
        spec = declared.get(key)
        location = spec.location if spec else None
        if location == "query":
            query[key] = _coerce_value(value)
        elif location == "form":
            form[key] = _coerce_value(value)
        elif method.http_method in WRITE_METHODS:
            form[key] = _coerce_value(value)
        else:
            query[key] = _coerce_value(value)
    return query, form


def _check_required(method: Method, params: dict[str, Any]) -> None:
    for spec in method.params:
        if spec.required and spec.name not in params:
            raise xmlrpc.client.Fault(
                FAULT_MISSING_PARAM,
                f"Missing required parameter '{spec.name}' for {method.name}.",
            )


def _decode(response: Any) -> Any:
    content_type = response.headers.get("content-type", "")
    if "application/json" in content_type:
        try:
            return response.json()
        except ValueError:
            return response.text
    return response.text


def call(method: Method, params: dict[str, Any], auth: dict[str, str]) -> Any:
    if not isinstance(params, dict):
        raise xmlrpc.client.Fault(
            FAULT_INVALID_PARAMS,
            f"{method.name} expects a single struct of named parameters.",
        )
    params = dict(params)
    api_key = str(params.pop("api_key", "") or auth.get("api_key", ""))
    _check_required(method, params)
    path = _build_path(method, params)
    query, form = _split_params(method, params)

    headers = {
        "Accept": "application/json",
        "X-Requested-With": "fetch",
    }
    if api_key:
        headers["Authorization"] = f"Bearer {api_key}"
        headers["X-API-KEY"] = api_key
    elif auth.get("authorization"):
        headers["Authorization"] = auth["authorization"]
    if auth.get("real_ip"):
        headers["X-Real-IP"] = auth["real_ip"]
    if auth.get("forwarded_for"):
        headers["X-Forwarded-For"] = auth["forwarded_for"]

    logger.debug("Bridge %s -> %s %s", method.name, method.http_method, path)
    try:
        with stealth.stealth_sync_client(
            base_url=INTERNAL_BASE_URL,
            timeout=TIMEOUT_SECONDS,
            follow_redirects=True,
            headers=headers,
        ) as client:
            response = client.request(
                method.http_method,
                path,
                params=query or None,
                data=form or None,
            )
    except Exception as exc:
        logger.warning("Bridge transport error for %s: %r", method.name, exc)
        raise xmlrpc.client.Fault(
            FAULT_TRANSPORT, f"Transport error calling {method.name}"
        )

    body = _decode(response)
    if response.status_code >= 400:
        message = body
        if isinstance(body, dict):
            error = body.get("error")
            if isinstance(error, dict):
                message = error.get("message", body)
            elif error:
                message = error
            else:
                message = body.get("detail", body)
        raise xmlrpc.client.Fault(response.status_code, _stringify(message))
    return body


def _stringify(message: Any) -> str:
    if isinstance(message, str):
        return message
    return str(message)
