"""V1 Payment Verifier — facilitator client for /api/v1/* endpoints."""

from __future__ import annotations

import json
import time
import urllib.request
import urllib.error
from typing import Literal, Optional

from .types import (
    NetworkType,
    PaymentStatus,
    VerifiedPayment,
    VerificationOptions,
    FacilitatorVerifyRequest,
    FacilitatorVerifyResponse,
    FacilitatorSettleRequest,
    FacilitatorSettleResponse,
    TokenType,
)


class SettleOptionsV1:
    """Options for settling a payment via the V1 facilitator."""

    def __init__(
        self,
        *,
        expected_recipient: str,
        min_amount: int,
        expected_sender: Optional[str] = None,
        token_type: Optional[TokenType] = None,
        resource: Optional[str] = None,
        method: Optional[str] = None,
    ) -> None:
        self.expected_recipient = expected_recipient
        self.min_amount = min_amount
        self.expected_sender = expected_sender
        self.token_type = token_type
        self.resource = resource
        self.method = method


class X402PaymentVerifierV1:
    """Payment verifier using the V1 facilitator API."""

    def __init__(
        self,
        facilitator_url: str = "http://localhost:8085",
        network: NetworkType = "mainnet",
    ) -> None:
        self._facilitator_url = facilitator_url.rstrip("/")
        self._network = network

    def _post(self, path: str, body: dict) -> dict:
        """Make a POST request to the facilitator."""
        url = f"{self._facilitator_url}{path}"
        data = json.dumps(body).encode()
        req = urllib.request.Request(
            url,
            data=data,
            headers={"Content-Type": "application/json", "Accept": "application/json"},
            method="POST",
        )
        with urllib.request.urlopen(req, timeout=15) as resp:
            return json.loads(resp.read())

    @staticmethod
    def _map_token_type(token_type: Optional[TokenType]) -> str:
        if token_type == "sBTC":
            return "SBTC"
        if token_type == "USDCx":
            return "USDCX"
        return "STX"

    @staticmethod
    def _map_status(status: Optional[str]) -> PaymentStatus:
        if status == "confirmed":
            return "success"
        if status == "pending":
            return "pending"
        if status == "failed":
            return "failed"
        return "not_found"

    def verify_payment(
        self, tx_id: str, options: VerificationOptions
    ) -> VerifiedPayment:
        """Verify a payment transaction using facilitator API."""
        try:
            body: dict = {
                "tx_id": tx_id,
                "expected_recipient": options.expected_recipient,
                "min_amount": options.min_amount,
                "network": self._network,
                "token_type": self._map_token_type(options.token_type),
            }
            if options.expected_sender:
                body["expected_sender"] = options.expected_sender
            if options.expected_memo:
                body["expected_memo"] = options.expected_memo
            if options.resource:
                body["resource"] = options.resource
            if options.method:
                body["method"] = options.method

            data = self._post("/api/v1/verify", body)
            resp = FacilitatorVerifyResponse.model_validate(data)

            return VerifiedPayment(
                tx_id=resp.tx_id or tx_id,
                status=self._map_status(resp.status),
                sender=resp.sender_address or "",
                recipient=resp.recipient_address or "",
                amount=resp.amount or 0,
                memo=resp.memo,
                block_height=resp.block_height,
                is_valid=resp.valid,
                validation_error=(
                    ", ".join(resp.validation_errors) if resp.validation_errors else None
                ),
            )
        except urllib.error.HTTPError as exc:
            try:
                err_data = json.loads(exc.read())
            except Exception:
                err_data = {}
            return self._error_payment(tx_id, err_data)
        except Exception as exc:
            return VerifiedPayment(
                tx_id=tx_id,
                status="not_found",
                sender="",
                recipient="",
                amount=0,
                is_valid=False,
                validation_error=str(exc),
            )

    def _error_payment(self, tx_id: str, err_data: dict) -> VerifiedPayment:
        if err_data.get("valid") is False:
            errors = err_data.get("validation_errors") or []
            return VerifiedPayment(
                tx_id=tx_id,
                status="not_found",
                sender=err_data.get("sender_address", ""),
                recipient=err_data.get("recipient_address", ""),
                amount=int(err_data.get("amount", 0)),
                memo=err_data.get("memo"),
                block_height=err_data.get("block_height"),
                is_valid=False,
                validation_error=", ".join(errors) if errors else "Payment validation failed",
            )
        return VerifiedPayment(
            tx_id=tx_id,
            status="not_found",
            sender="",
            recipient="",
            amount=0,
            is_valid=False,
            validation_error=err_data.get("error", "Verification failed"),
        )

    def is_payment_valid(
        self, tx_id: str, options: VerificationOptions
    ) -> bool:
        """Quick boolean check."""
        v = self.verify_payment(tx_id, options)
        return v.is_valid and v.status == "success"

    def settle_payment(
        self, signed_transaction: str, options: SettleOptionsV1
    ) -> VerifiedPayment:
        """Settle a signed transaction via facilitator."""
        try:
            body: dict = {
                "signed_transaction": signed_transaction,
                "expected_recipient": options.expected_recipient,
                "min_amount": options.min_amount,
                "network": self._network,
                "token_type": self._map_token_type(options.token_type),
            }
            if options.expected_sender:
                body["expected_sender"] = options.expected_sender
            if options.resource:
                body["resource"] = options.resource
            if options.method:
                body["method"] = options.method

            data = self._post("/api/v1/settle", body)
            resp = FacilitatorSettleResponse.model_validate(data)

            status: PaymentStatus = "failed"
            if resp.success and resp.status == "confirmed":
                status = "success"
            elif resp.status == "pending":
                status = "pending"

            return VerifiedPayment(
                tx_id=resp.tx_id or "",
                status=status,
                sender=resp.sender_address or "",
                recipient=resp.recipient_address or "",
                amount=resp.amount or 0,
                block_height=resp.block_height,
                is_valid=resp.success is True and resp.status == "confirmed",
                validation_error=(
                    ", ".join(resp.validation_errors)
                    if resp.validation_errors
                    else resp.error
                ),
            )
        except urllib.error.HTTPError as exc:
            try:
                err_data = json.loads(exc.read())
            except Exception:
                err_data = {}
            if err_data.get("success") is False:
                errors = err_data.get("validation_errors") or []
                return VerifiedPayment(
                    tx_id=err_data.get("tx_id", ""),
                    status="failed",
                    sender=err_data.get("sender_address", ""),
                    recipient=err_data.get("recipient_address", ""),
                    amount=int(err_data.get("amount", 0)),
                    block_height=err_data.get("block_height"),
                    is_valid=False,
                    validation_error=(
                        ", ".join(errors)
                        if errors
                        else err_data.get("error", "Settlement failed")
                    ),
                )
            return VerifiedPayment(
                tx_id="", status="failed", sender="", recipient="",
                amount=0, is_valid=False, validation_error="Settlement failed",
            )
        except Exception as exc:
            return VerifiedPayment(
                tx_id="", status="failed", sender="", recipient="",
                amount=0, is_valid=False, validation_error=str(exc),
            )

    def wait_for_confirmation(
        self,
        tx_id: str,
        options: VerificationOptions,
        max_attempts: int = 20,
        interval_s: float = 30.0,
    ) -> VerifiedPayment | None:
        """Wait for transaction confirmation with polling."""
        for _ in range(max_attempts):
            v = self.verify_payment(tx_id, options)
            if v.is_valid and v.status == "success":
                return v
            if v.status == "failed":
                raise RuntimeError(f"Transaction failed: {v.validation_error}")
            time.sleep(interval_s)
        return None
