"""
Diogenes Lightning L402 Client (Python)
Zero-dependency client for invoking Diogenes edge microservices with automatic
Lightning Network L402 HTTP 402 payment negotiation or zero-cost demo preview.
"""

import json
import logging
import re
from typing import Any, Callable, Dict, Optional
import urllib.request
import urllib.error

logger = logging.getLogger("diogenes.l402")

DEFAULT_GATEWAY_URL = "https://diogenes-micro-gateway.corinth.workers.dev"


class DiogenesL402Client:
    """
    Python client for Diogenes sovereign edge microservices using the L402 protocol.
    """

    def __init__(
        self,
        gateway_url: str = DEFAULT_GATEWAY_URL,
        coinos_token: Optional[str] = None,
        payment_handler: Optional[Callable[[str], str]] = None,
        demo_mode: bool = False,
    ):
        """
        Initialize the Diogenes L402 client.

        :param gateway_url: Base URL of the Diogenes edge worker.
        :param coinos_token: Optional Coinos API token for automated satoshi settlement.
        :param payment_handler: Optional callback function that accepts a BOLT11 invoice
                                string and returns the hex payment preimage.
        :param demo_mode: If True, appends ?demo=true for zero-fee trial execution.
        """
        self.gateway_url = gateway_url.rstrip("/")
        self.coinos_token = coinos_token
        self.payment_handler = payment_handler
        self.demo_mode = demo_mode

    def _pay_via_coinos(self, bolt11_invoice: str) -> str:
        """
        Pay a BOLT11 invoice via Coinos API and return the preimage.
        """
        if not self.coinos_token:
            raise ValueError("No Coinos token configured for automated settlement.")

        url = "https://coinos.io/api/lightning/send"
        data = json.dumps({"invoice": bolt11_invoice}).encode("utf-8")
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self.coinos_token}",
        }

        req = urllib.request.Request(url, data=data, headers=headers, method="POST")
        with urllib.request.urlopen(req, timeout=15) as resp:
            res_json = json.loads(resp.read().decode("utf-8"))
            preimage = res_json.get("preimage") or res_json.get("hash")
            if not preimage:
                raise RuntimeError(f"Coinos payment did not return preimage: {res_json}")
            return preimage

    def _parse_l402_header(self, auth_header: str) -> Dict[str, str]:
        """
        Extract macaroon and invoice from WWW-Authenticate header.
        Format: L402 macaroon="...", invoice="..."
        """
        macaroon_match = re.search(r'macaroon="([^"]+)"', auth_header)
        invoice_match = re.search(r'invoice="([^"]+)"', auth_header)

        macaroon = macaroon_match.group(1) if macaroon_match else ""
        invoice = invoice_match.group(1) if invoice_match else ""

        return {"macaroon": macaroon, "invoice": invoice}

    def call_microservice(
        self, endpoint: str, payload: Dict[str, Any]
    ) -> Dict[str, Any]:
        """
        Invoke a Diogenes microservice with automatic L402 handling.

        :param endpoint: Route path (e.g. '/api/v1/agent/guardrail-verify').
        :param payload: JSON body parameters.
        :return: Microservice response JSON.
        """
        if not endpoint.startswith("/"):
            endpoint = "/" + endpoint

        url = f"{self.gateway_url}{endpoint}"
        if self.demo_mode and "?demo=true" not in url:
            url += "?demo=true"

        data = json.dumps(payload).encode("utf-8")
        headers = {
            "Content-Type": "application/json",
            "User-Agent": "Diogenes-Python-L402/1.0",
        }

        req = urllib.request.Request(url, data=data, headers=headers, method="POST")

        try:
            with urllib.request.urlopen(req, timeout=15) as resp:
                return json.loads(resp.read().decode("utf-8"))
        except urllib.error.HTTPError as err:
            if err.code == 402 and not self.demo_mode:
                # 402 Payment Required
                auth_header = err.headers.get("WWW-Authenticate", "")
                parsed = self._parse_l402_header(auth_header)
                macaroon = parsed["macaroon"]
                invoice = parsed["invoice"]

                if not invoice:
                    # Fallback to reading JSON challenge
                    err_json = json.loads(err.read().decode("utf-8"))
                    invoice = err_json.get("payment_request") or err_json.get("invoice", "")
                    macaroon = err_json.get("macaroon", "")

                if not invoice or not macaroon:
                    return {
                        "error": "L402_CHALLENGE_FAILED",
                        "message": "Missing macaroon or invoice in 402 challenge.",
                    }

                # Settle invoice
                logger.info("Paying Lightning invoice for L402 challenge...")
                if self.payment_handler:
                    preimage = self.payment_handler(invoice)
                elif self.coinos_token:
                    preimage = self._pay_via_coinos(invoice)
                else:
                    return {
                        "error": "PAYMENT_REQUIRED",
                        "invoice": invoice,
                        "macaroon": macaroon,
                        "notice": "Provide coinos_token or payment_handler, or use demo_mode=True.",
                    }

                # Retry request with preimage
                retry_headers = {
                    "Content-Type": "application/json",
                    "Authorization": f"L402 {macaroon}:{preimage}",
                    "User-Agent": "Diogenes-Python-L402/1.0",
                }
                retry_req = urllib.request.Request(
                    url, data=data, headers=retry_headers, method="POST"
                )
                with urllib.request.urlopen(retry_req, timeout=15) as retry_resp:
                    return json.loads(retry_resp.read().decode("utf-8"))
            else:
                try:
                    return json.loads(err.read().decode("utf-8"))
                except Exception:
                    return {"error": "HTTP_ERROR", "status": err.code, "message": str(err)}
