"""
CashPay Python SDK
Official SDK for integrating with CashPay Payment Gateway
"""

import hashlib
import hmac
import time
import json
import urllib.parse
from typing import Any, Dict, List, Optional, Union
from dataclasses import dataclass
import requests


__version__ = "1.1.0"
__all__ = ["CashPay", "CashPayError"]


class CashPayError(Exception):
    """Exception raised for CashPay API errors."""

    def __init__(self, message: str, status_code: int = 0, code: str = "UNKNOWN_ERROR", details: Any = None):
        super().__init__(message)
        self.message = message
        self.status_code = status_code
        self.code = code
        self.details = details

    def __str__(self):
        return f"CashPayError({self.code}): {self.message}"


@dataclass
class Balance:
    settlement_balance: int
    payout_balance: int
    total_balance: int


@dataclass
class SettlementBalance:
    available_withdrawal_amount: int
    total_settlement_amount: int
    rolling_reserve_remaining: int


@dataclass
class PayoutBalance:
    payout_balance: int
    payout_initiated_amount: int
    success_deposit_amount: int
    initiated_deposit_amount: int


class BalanceAPI:
    """Balance API endpoints."""

    def __init__(self, client: "CashPay"):
        self._client = client

    def get(self) -> Balance:
        """Get unified balance (settlement + payout)."""
        data = self._client._request("GET", "/v1/balance")
        return Balance(
            settlement_balance=data["settlementBalance"],
            payout_balance=data["payoutBalance"],
            total_balance=data["totalBalance"],
        )

    def get_settlement(self) -> SettlementBalance:
        """Get settlement balance details."""
        data = self._client._request("GET", "/v1/balance/settlement")
        return SettlementBalance(
            available_withdrawal_amount=data["availableWithdrawalAmount"],
            total_settlement_amount=data["totalSettlementAmount"],
            rolling_reserve_remaining=data["rollingReserveRemaining"],
        )

    def get_payout(self) -> PayoutBalance:
        """Get payout balance details."""
        data = self._client._request("GET", "/v1/balance/payout")
        return PayoutBalance(
            payout_balance=data["payoutBalance"],
            payout_initiated_amount=data["payoutInitiatedAmount"],
            success_deposit_amount=data["successDepositAmount"],
            initiated_deposit_amount=data["initiatedDepositAmount"],
        )


class PayinAPI:
    """Payin API endpoints."""

    def __init__(self, client: "CashPay"):
        self._client = client

    def get_by_order_id(self, order_id: str) -> Dict[str, Any]:
        """Get payin by order ID."""
        return self._client._request("GET", "/v1/payin", params={"orderId": order_id})

    def get_status(self, payment_id: str) -> Dict[str, Any]:
        """Get payin status by payment ID."""
        return self._client._request("GET", f"/v1/payin/{payment_id}/status")

    def create_intent(self, params: Dict[str, Any], idempotency_key: Optional[str] = None) -> Dict[str, Any]:
        """Create intent payment (UPI)."""
        headers = {}
        if idempotency_key:
            headers["x-idempotency-key"] = idempotency_key
        return self._client._request("POST", "/v1/payin/intent", json_data=params, headers=headers)

    def create_card(self, params: Dict[str, Any], idempotency_key: Optional[str] = None) -> Dict[str, Any]:
        """Create card payment."""
        headers = {}
        if idempotency_key:
            headers["x-idempotency-key"] = idempotency_key
        return self._client._request("POST", "/v1/payin/card", json_data=params, headers=headers)

    def create_netbanking(self, params: Dict[str, Any], idempotency_key: Optional[str] = None) -> Dict[str, Any]:
        """Create netbanking payment."""
        headers = {}
        if idempotency_key:
            headers["x-idempotency-key"] = idempotency_key
        return self._client._request("POST", "/v1/payin/netbanking", json_data=params, headers=headers)

    def create_upi_collect(self, params: Dict[str, Any], idempotency_key: Optional[str] = None) -> Dict[str, Any]:
        """Create UPI collect payment."""
        headers = {}
        if idempotency_key:
            headers["x-idempotency-key"] = idempotency_key
        return self._client._request("POST", "/v1/payin/upi-collect", json_data=params, headers=headers)

    def create_wallet(self, params: Dict[str, Any], idempotency_key: Optional[str] = None) -> Dict[str, Any]:
        """Create wallet payment."""
        headers = {}
        if idempotency_key:
            headers["x-idempotency-key"] = idempotency_key
        return self._client._request("POST", "/v1/payin/wallet", json_data=params, headers=headers)


class PayoutAPI:
    """Payout API endpoints."""

    def __init__(self, client: "CashPay"):
        self._client = client

    def create(
        self,
        beneficiary_id: str,
        amount: int,
        reference_id: Optional[str] = None,
        narration: Optional[str] = None,
        mode: str = "IMPS",
        idempotency_key: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Create a new payout."""
        data = {
            "beneficiaryId": beneficiary_id,
            "amount": amount,
            "mode": mode,
        }
        if reference_id:
            data["referenceId"] = reference_id
        if narration:
            data["narration"] = narration

        headers = {}
        if idempotency_key:
            headers["x-idempotency-key"] = idempotency_key

        return self._client._request("POST", "/v1/payouts", json_data=data, headers=headers)

    def create_bulk(
        self,
        payouts: List[Dict[str, Any]],
        idempotency_key: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Create multiple payouts (max 100)."""
        headers = {}
        if idempotency_key:
            headers["x-idempotency-key"] = idempotency_key

        return self._client._request("POST", "/v1/payouts/bulk", json_data={"payouts": payouts}, headers=headers)

    def list(
        self,
        page: int = 1,
        limit: int = 20,
        status: Optional[str] = None,
        reference_id: Optional[str] = None,
    ) -> Dict[str, Any]:
        """List payouts with pagination."""
        params = {"page": page, "limit": limit}
        if status:
            params["status"] = status
        if reference_id:
            params["referenceId"] = reference_id
        return self._client._request("GET", "/v1/payouts", params=params)

    def get(self, payout_id: str) -> Dict[str, Any]:
        """Get payout by ID."""
        return self._client._request("GET", f"/v1/payouts/{payout_id}")

    def get_by_reference_id(self, reference_id: str) -> Dict[str, Any]:
        """Get payout by reference ID."""
        result = self.list(reference_id=reference_id)
        if result["data"]:
            return result["data"][0]
        raise CashPayError(f"Payout not found with reference_id: {reference_id}", 404, "NOT_FOUND")

    def cancel(self, payout_id: str) -> Dict[str, Any]:
        """Cancel a payout (only CREATED or BLOCKED status)."""
        return self._client._request("POST", f"/v1/payouts/{payout_id}/cancel")


class SettlementAPI:
    """Settlement API endpoints."""

    def __init__(self, client: "CashPay"):
        self._client = client

    def create(
        self,
        amount: int,
        bank_account_id: Optional[str] = None,
        account_number: Optional[str] = None,
        ifsc: Optional[str] = None,
        account_holder_name: Optional[str] = None,
        reference_id: Optional[str] = None,
        idempotency_key: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Create a new settlement."""
        data = {"amount": amount}
        if bank_account_id:
            data["bankAccountId"] = bank_account_id
        if account_number:
            data["accountNumber"] = account_number
        if ifsc:
            data["ifsc"] = ifsc
        if account_holder_name:
            data["accountHolderName"] = account_holder_name
        if reference_id:
            data["referenceId"] = reference_id

        headers = {}
        if idempotency_key:
            headers["x-idempotency-key"] = idempotency_key

        return self._client._request("POST", "/v1/settlements", json_data=data, headers=headers)

    def create_bulk(
        self,
        settlements: List[Dict[str, Any]],
        idempotency_key: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Create multiple settlements (max 100)."""
        headers = {}
        if idempotency_key:
            headers["x-idempotency-key"] = idempotency_key

        return self._client._request("POST", "/v1/settlements/bulk", json_data={"settlements": settlements}, headers=headers)

    def list(
        self,
        page: int = 1,
        limit: int = 20,
        status: Optional[str] = None,
        reference_id: Optional[str] = None,
    ) -> Dict[str, Any]:
        """List settlements with pagination."""
        params = {"page": page, "limit": limit}
        if status:
            params["status"] = status
        if reference_id:
            params["referenceId"] = reference_id
        return self._client._request("GET", "/v1/settlements", params=params)

    def get(self, settlement_id: str) -> Dict[str, Any]:
        """Get settlement by ID."""
        return self._client._request("GET", f"/v1/settlements/{settlement_id}")

    def get_by_reference_id(self, reference_id: str) -> Dict[str, Any]:
        """Get settlement by reference ID."""
        result = self.list(reference_id=reference_id)
        if result["data"]:
            return result["data"][0]
        raise CashPayError(f"Settlement not found with reference_id: {reference_id}", 404, "NOT_FOUND")

    def cancel(self, settlement_id: str) -> Dict[str, Any]:
        """Cancel a settlement."""
        return self._client._request("POST", f"/v1/settlements/{settlement_id}/cancel")


class BeneficiaryAPI:
    """Beneficiary API endpoints."""

    def __init__(self, client: "CashPay"):
        self._client = client

    def create(self, params: Dict[str, Any]) -> Dict[str, Any]:
        return self._client._request("POST", "/v1/beneficiaries", json_data=params)

    def list(self, page: int = 1, limit: int = 20) -> Dict[str, Any]:
        return self._client._request("GET", "/v1/beneficiaries", params={"page": page, "limit": limit})

    def get(self, beneficiary_id: str) -> Dict[str, Any]:
        return self._client._request("GET", f"/v1/beneficiaries/{beneficiary_id}")

    def update(self, beneficiary_id: str, params: Dict[str, Any]) -> Dict[str, Any]:
        return self._client._request("PATCH", f"/v1/beneficiaries/{beneficiary_id}", json_data=params)

    def delete(self, beneficiary_id: str) -> Dict[str, Any]:
        return self._client._request("DELETE", f"/v1/beneficiaries/{beneficiary_id}")


class BankAccountAPI:
    """Bank Account API endpoints."""

    def __init__(self, client: "CashPay"):
        self._client = client

    def create(self, params: Dict[str, Any]) -> Dict[str, Any]:
        return self._client._request("POST", "/v1/bank-accounts", json_data=params)

    def list(self) -> Dict[str, Any]:
        return self._client._request("GET", "/v1/bank-accounts")

    def get(self, account_id: str) -> Dict[str, Any]:
        return self._client._request("GET", f"/v1/bank-accounts/{account_id}")

    def delete(self, account_id: str) -> Dict[str, Any]:
        return self._client._request("DELETE", f"/v1/bank-accounts/{account_id}")


class CashPay:
    """
    CashPay API Client.
    """

    def __init__(
        self,
        api_key: str,
        api_secret: str,
        environment: str = "production",
        base_url: Optional[str] = None,
        timeout: int = 30,
    ):
        self.api_key = api_key
        self.api_secret = api_secret
        self.timeout = timeout

        if base_url:
            self.base_url = base_url.rstrip("/")
        elif environment == "sandbox":
            self.base_url = "https://sandbox.cashpay.com/api"
        else:
            self.base_url = "https://api.cashpay.com/api"

        self._session = requests.Session()

        # Initialize API modules
        self.balance = BalanceAPI(self)
        self.payins = PayinAPI(self)
        self.payouts = PayoutAPI(self)
        self.settlements = SettlementAPI(self)
        self.beneficiaries = BeneficiaryAPI(self)
        self.bank_accounts = BankAccountAPI(self)

    def _generate_signature(self, timestamp: str, method: str, path: str, body: str) -> str:
        """Generate HMAC-SHA256 signature for request authentication."""
        message = f"{timestamp}{method}{path}{body}"
        return hmac.new(
            self.api_secret.encode("utf-8"),
            message.encode("utf-8"),
            hashlib.sha256,
        ).hexdigest()

    def _request(
        self,
        method: str,
        path: str,
        params: Optional[Dict[str, Any]] = None,
        json_data: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> Dict[str, Any]:
        """Make an authenticated API request."""
        url = f"{self.base_url}{path}"
        timestamp = str(int(time.time() * 1000))
        
        body = ""
        if json_data is not None:
            # Match Node.js/PHP default representation which excludes spaces
            body = json.dumps(json_data, separators=(',', ':'))

        # Prepare path with query string for signature correctly
        signature_path = path
        if params:
            query_string = urllib.parse.urlencode(params)
            signature_path = f"{path}?{query_string}"

        signature = self._generate_signature(timestamp, method, signature_path, body)

        request_headers = {
            "Content-Type": "application/json",
            "x-api-key": self.api_key,
            "x-timestamp": timestamp,
            "x-signature": signature,
        }
        if headers:
            request_headers.update(headers)

        try:
            response = self._session.request(
                method=method,
                url=url,
                params=params,
                data=body.encode('utf-8') if body else None,
                headers=request_headers,
                timeout=self.timeout,
            )

            if response.status_code >= 400:
                try:
                    error_data = response.json()
                    raise CashPayError(
                        message=error_data.get("message", "Unknown error"),
                        status_code=response.status_code,
                        code=error_data.get("error", "UNKNOWN_ERROR"),
                        details=error_data.get("details"),
                    )
                except ValueError:
                    raise CashPayError(
                        message=response.text,
                        status_code=response.status_code,
                        code="UNKNOWN_ERROR",
                    )

            if response.text:
                return response.json()
            return {}

        except requests.exceptions.Timeout:
            raise CashPayError("Request timed out", 408, "TIMEOUT")
        except requests.exceptions.ConnectionError:
            raise CashPayError("Connection error", 503, "CONNECTION_ERROR")

    def verify_webhook(self, payload: str, signature: str, secret: str) -> bool:
        """
        Verify webhook signature.
        """
        expected = hmac.new(
            secret.encode("utf-8"),
            payload.encode("utf-8"),
            hashlib.sha256,
        ).hexdigest()
        return hmac.compare_digest(signature, expected)
