import frappe
import requests
from kapital_bank.auth import refresh_token, get_default_login, BROWSER_HEADERS

# Root host. Auth (login/refresh) still lives under /api/b2b (see auth.py); all
# other v2 portal endpoints are passed as full paths starting with /b2b/...
BASE_URL = "https://my.birbank.business"


def extract_error_message(resp):
    """Best-effort human-readable error from a bank response.

    Includes field-level validation errors — the v2 portal returns
    {"message": "...", "errors": [{"property": "...", "message": "..."}]}.
    """
    try:
        body = resp.json()
    except ValueError:
        return (resp.text[:500] if resp.text else "") or f"HTTP {resp.status_code}"
    if not isinstance(body, dict):
        return str(body)

    msg = body.get("response", {}).get("message") or body.get("message") or ""
    details = body.get("errors")
    if isinstance(details, list):
        parts = []
        for d in details:
            if isinstance(d, dict) and d.get("message"):
                prop = d.get("property")
                parts.append(f"{prop}: {d['message']}" if prop else d["message"])
        if parts:
            msg = (f"{msg} — " if msg else "") + "; ".join(parts)
    return msg or str(body) or f"HTTP {resp.status_code}"


class BIRBankClient:
    """HTTP client for BIRBank B2B API with automatic token refresh on 401."""

    def __init__(self, login_name=None):
        if not login_name:
            login_name = get_default_login()
        if not login_name:
            frappe.throw("No authenticated Kapital Bank Login found")
        self.login_name = login_name
        self._load_token()

    def _load_token(self):
        doc = frappe.get_doc("Kapital Bank Login", self.login_name)
        self.token = doc.get_password("jwt_token") if doc.jwt_token else ""

    def _headers(self):
        return {**BROWSER_HEADERS, "Authorization": f"Bearer {self.token}"}

    def _request(self, method, path, **kwargs):
        url = f"{BASE_URL}{path}"
        kwargs.setdefault("timeout", 30)

        resp = requests.request(method, url, headers=self._headers(), **kwargs)

        if resp.status_code == 401:
            # Try refreshing once
            frappe.logger().info(f"[KB] 401 on {path}, refreshing token for {self.login_name}")
            self.token = refresh_token(self.login_name)
            resp = requests.request(method, url, headers=self._headers(), **kwargs)

        if not resp.ok:
            frappe.log_error(
                f"[KB] {method} {url} → HTTP {resp.status_code}\nResponse body: {resp.text[:2000]}",
                "KB API Response Error"
            )

        return resp

    def get(self, path, params=None):
        return self._request("GET", path, params=params)

    def post(self, path, json=None, data=None, files=None, headers_override=None):
        if files:
            # For multipart requests, don't set Content-Type (requests sets it automatically)
            hdrs = {k: v for k, v in BROWSER_HEADERS.items() if k != "Content-Type"}
            hdrs["Authorization"] = f"Bearer {self.token}"
            url = f"{BASE_URL}{path}"
            resp = requests.post(url, headers=hdrs, data=data, files=files, timeout=30)
            if resp.status_code == 401:
                self.token = refresh_token(self.login_name)
                hdrs["Authorization"] = f"Bearer {self.token}"
                resp = requests.post(url, headers=hdrs, data=data, files=files, timeout=30)
            if not resp.ok:
                frappe.log_error(
                    f"[KB] POST {url} (multipart) → HTTP {resp.status_code}\nResponse body: {resp.text[:2000]}",
                    "KB API Response Error"
                )
            return resp
        return self._request("POST", path, json=json)

    def put(self, path, json=None, params=None):
        return self._request("PUT", path, json=json, params=params)

    def delete(self, path, params=None):
        return self._request("DELETE", path, params=params)

    def get_json(self, path, params=None):
        resp = self.get(path, params=params)
        self._check_response(resp)
        return resp.json()

    def post_json(self, path, json=None):
        resp = self.post(path, json=json)
        self._check_response(resp)
        return resp.json()

    def put_json(self, path, json=None, params=None):
        resp = self.put(path, json=json, params=params)
        self._check_response(resp)
        return resp.json()

    def _check_response(self, resp):
        """Raise with the bank's own message (incl. field errors) if available."""
        if resp.ok:
            return
        msg = extract_error_message(resp)
        raise requests.HTTPError(msg or f"HTTP {resp.status_code}", response=resp)
