"""
ByteMatrix - Slip Verification SDK for Python
© 2026 ByteMatrix Co., Ltd. All Rights Reserved.

Installation:
    pip install requests

Usage:
    from slip_verify_sdk import SlipVerifySDK
    client = SlipVerifySDK('your-app-id', 'your-api-key')
"""

import hashlib
import hmac
import re
from urllib.parse import quote
from typing import Optional, Dict, Any
import requests


class SlipVerifySDK:
    """Slip Verification API Client for Python"""

    def __init__(
        self,
        app_id: str,
        api_key: str,
        base_url: str = "https://api.slipmatrix.com",
        timeout: int = 30
    ):
        self.app_id = app_id
        self.api_key = api_key
        self.base_url = base_url
        self.timeout = timeout
        self.headers = {
            "x-app-id": self.app_id,
            "x-api-key": self.api_key,
            "Content-Type": "application/json",
        }

    def verify_slip(
        self,
        trans_ref: str,
        sending_bank: str,
        callback_url: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
        *, rules: Optional[Dict[str, Any]] = None, order_id: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Verify a payment slip using transaction reference

        Args:
            trans_ref: Transaction reference from QR code
            sending_bank: Bank code of the sender (e.g., '014' for SCB)
            callback_url: Deprecated and unsupported; configure the merchant webhook
            metadata: Deprecated and unsupported
            rules: Additional acceptance rules
            order_id: Registered payment order ID

        Returns:
            Verification submission envelope (not payment proof)
        """
        payload = {
            "transRef": trans_ref,
            "sendingBank": sending_bank,
        }
        if callback_url is not None or metadata is not None:
            raise ValueError("Per-request callbackUrl/metadata are unsupported; configure the merchant webhook")
        if rules is not None: payload["rules"] = rules
        if order_id is not None: payload["orderId"] = order_id

        response = requests.post(
            f"{self.base_url}/api/verify",
            json=payload,
            headers=self.headers,
            timeout=self.timeout
        )
        response.raise_for_status()
        return response.json()

    def verify_from_qr(self, qr_data: str, rules: Optional[Dict[str, Any]] = None, order_id: Optional[str] = None) -> Dict[str, Any]:
        """
        Verify a payment slip using QR code data

        Args:
            qr_data: Raw QR code data string

        Returns:
            Verification result dictionary
        """
        response = requests.post(
            f"{self.base_url}/api/verify/qr",
            json={"qrData": qr_data, **({"rules": rules} if rules is not None else {}), **({"orderId": order_id} if order_id is not None else {})},
            headers=self.headers,
            timeout=self.timeout
        )
        response.raise_for_status()
        return response.json()

    def verify_from_url(self, image_url: str, rules: Optional[Dict[str, Any]] = None, order_id: Optional[str] = None) -> Dict[str, Any]:
        payload = {"imageUrl": image_url}
        if rules is not None:
            payload["rules"] = rules
        if order_id is not None: payload["orderId"] = order_id
        response = requests.post(f"{self.base_url}/api/verify/image/url", json=payload,
                                 headers=self.headers, timeout=self.timeout)
        response.raise_for_status()
        return response.json()

    def _order_request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
        response = requests.request(method, f"{self.base_url}/api/orders{endpoint}",
                                    headers=self.headers, timeout=self.timeout, **kwargs)
        response.raise_for_status()
        return response.json()

    def verify_true_money_file(self, file_path: str, *, mime_type: str = "image/png",
                               rules: Optional[Dict[str, Any]] = None,
                               order_id: Optional[str] = None) -> Dict[str, Any]:
        """Submit a local image; a receipt is not proof of payment.

        Keep the original bytes, MIME, rules and order_id for uncertain retries.
        No automatic retry or redirect is performed. Requires provider activation.
        """
        extensions = {"image/png": "png", "image/jpeg": "jpg", "image/webp": "webp", "image/gif": "gif"}
        if mime_type not in extensions:
            raise ValueError("Unsupported Wallet image MIME type")
        with open(file_path, "rb") as slip:
            image = slip.read(4 * 1024 * 1024 + 1)
        if not image or len(image) > 4 * 1024 * 1024:
            raise ValueError("Wallet image must contain 1–4194304 bytes")
        import json
        fields = {}
        if rules is not None: fields["rules"] = json.dumps(rules, separators=(",", ":"))
        if order_id is not None: fields["orderId"] = order_id
        response = requests.post(
            f"{self.base_url}/api/verify/truewallet/file",
            headers={key: value for key, value in self.headers.items() if key.lower() != "content-type"},
            files={"file": ("slip." + extensions[mime_type], image, mime_type)},
            data=fields, timeout=self.timeout, allow_redirects=False,
        )
        response.raise_for_status()
        if response.status_code >= 300:
            raise RuntimeError("Wallet upload redirect rejected; check the configured API URL")
        body = response.json()
        data = body.get("data") if isinstance(body, dict) else None
        if (not isinstance(body, dict) or body.get("success") is not True or
                not isinstance(data, dict) or
                not re.fullmatch(r"[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}", str(data.get("transactionId", ""))) or
                data.get("status") not in ("PENDING", "PROCESSING", "VERIFIED", "FAILED", "DUPLICATE", "INVALID", "INSUFFICIENT_CREDIT")):
            raise RuntimeError("Wallet receipt is unavailable; check history before retrying the same image and context")
        return body

    def create_order(self, order: Dict[str, Any]) -> Dict[str, Any]:
        return self._order_request("POST", "", json=order)

    def get_order(self, order_id: str) -> Dict[str, Any]:
        return self._order_request("GET", "/" + quote(order_id, safe=""))

    def list_orders(self, page: int = 1, limit: int = 20, status: Optional[str] = None) -> Dict[str, Any]:
        return self._order_request("GET", "", params={"page": page, "limit": limit, **({"status": status} if status else {})})

    def cancel_order(self, order_id: str) -> Dict[str, Any]:
        return self._order_request("POST", "/" + quote(order_id, safe="") + "/cancel")

    def get_status(self, transaction_id: str) -> Dict[str, Any]:
        """
        Get transaction status by ID

        Args:
            transaction_id: Transaction ID from verify response

        Returns:
            Transaction status dictionary
        """
        response = requests.get(
            f"{self.base_url}/api/verify/{quote(transaction_id, safe='')}",
            headers=self.headers,
            timeout=self.timeout
        )
        response.raise_for_status()
        return response.json()

    def list_transactions(
        self,
        page: int = 1,
        limit: int = 20,
        status: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        List all transactions (paginated)

        Args:
            page: Page number (default 1)
            limit: Items per page (default 20)
            status: Filter by status (PENDING, VERIFIED, FAILED)

        Returns:
            Transaction list with pagination info
        """
        params = {"page": page, "limit": limit}
        if status:
            params["status"] = status

        response = requests.get(
            f"{self.base_url}/api/verify",
            params=params,
            headers=self.headers,
            timeout=self.timeout
        )
        response.raise_for_status()
        return response.json()

    def get_banks(self) -> Dict[str, Any]:
        """
        Get list of supported banks

        Returns:
            List of bank codes and names
        """
        response = requests.get(
            f"{self.base_url}/api/verify/banks",
            timeout=self.timeout
        )
        response.raise_for_status()
        return response.json()

    @staticmethod
    def verify_webhook_signature(
        payload: Any,
        signature: str,
        secret: str
    ) -> bool:
        """
        Verify webhook signature (HMAC-SHA256)

        Args:
            payload: Exact raw webhook request body (bytes or string)
            signature: X-Webhook-Signature header value
            secret: Your webhook secret

        Returns:
            True if signature is valid
        """
        if not isinstance(payload, (str, bytes)) or not isinstance(signature, str) or not re.fullmatch(r"[a-fA-F0-9]{64}", signature) or not isinstance(secret, str) or not secret:
            return False
        raw = payload.encode() if isinstance(payload, str) else payload
        expected = hmac.new(secret.encode(), raw, hashlib.sha256).hexdigest()
        return hmac.compare_digest(signature.lower(), expected)



# Example usage
if __name__ == "__main__":
    # Initialize client
    client = SlipVerifySDK(
        app_id="your-app-id",
        api_key="your-api-key",
        base_url="http://localhost:9920"
    )

    # Get supported banks
    banks = client.get_banks()
    print("Supported banks:", banks)

    # Verify a slip
    # result = client.verify_slip("202501291234567890", "014")
    # print("Verification result:", result)
