import requests
import json
from typing import Dict, List, Optional, Any, Union  # ← This line was missing
from .exceptions import (
    PaymentSDKError, AuthenticationError, ValidationError,
    RateLimitError, ServerError
)
from .types import (
    SDKConfig, PaymentRequest, InvoiceRequest, WithdrawalRequest,
    WebhookConfig, PaymentResponse, BalanceResponse, WithdrawalResponse,
    PaymentStatusResponse, WebhookDelivery,
    CreateStoreRequest, UpdateStoreRequest, CreateProductRequest,
    UpdateProductRequest, StoreResponse, ProductResponse,
    ProductListResponse, OrderResponse, OrderListResponse,
)
from .webhook import verify_webhook_signature, parse_webhook_event


class PaymentSDK:
    """InventPay Python SDK for crypto payment processing."""

    def __init__(self, config: SDKConfig):
        """
        Initialize the InventPay SDK.

        Args:
            config: SDK configuration with API key
        """
        if not config.api_key:
            raise ValueError("API key is required")

        self.api_key = config.api_key
        self.withdrawal_api_key = config.withdrawal_api_key
        self.base_url = config.base_url.rstrip('/')
        self.timeout = config.timeout
        self.session = requests.Session()
        self.session.headers.update({
            'Content-Type': 'application/json',
            'X-API-Key': self.api_key,
            'User-Agent': 'InventPay-Python-SDK/1.2.0'
        })

    def _request(self, method: str, endpoint: str, data: Optional[Dict] = None,
                 requires_auth: bool = True, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
        """
        Make HTTP request to InventPay API.

        Args:
            method: HTTP method (GET, POST, PUT, DELETE)
            endpoint: API endpoint
            data: Request data
            requires_auth: Whether API key is required
            extra_headers: Additional headers to include

        Returns:
            Response data as dictionary

        Raises:
            PaymentSDKError: For API errors
        """
        url = f"{self.base_url}{endpoint}"

        headers = {}
        if requires_auth:
            headers['X-API-Key'] = self.api_key
        if extra_headers:
            headers.update(extra_headers)

        try:
            response = self.session.request(
                method=method,
                url=url,
                json=data,
                timeout=self.timeout,
                headers=headers
            )

            # Handle non-JSON responses
            try:
                response_data = response.json()
            except ValueError:
                response_data = {'error': response.text}

            if not response.ok:
                error_msg = response_data.get('error', 'Request failed')
                details = response_data.get('details') or response_data.get('message')

                if response.status_code == 400:
                    raise ValidationError(error_msg, response.status_code, details)
                elif response.status_code == 401:
                    raise AuthenticationError(error_msg, response.status_code, details)
                elif response.status_code == 429:
                    raise RateLimitError(error_msg, response.status_code, details)
                elif response.status_code >= 500:
                    raise ServerError(error_msg, response.status_code, details)
                else:
                    raise PaymentSDKError(error_msg, response.status_code, details)

            return response_data

        except requests.exceptions.Timeout:
            raise PaymentSDKError("Request timeout", 408)
        except requests.exceptions.ConnectionError:
            raise PaymentSDKError("Connection error", None)
        except requests.exceptions.RequestException as e:
            raise PaymentSDKError(f"Request failed: {str(e)}", None)

    # ==================== PAYMENT METHODS ====================

    def create_invoice(self, invoice_data: InvoiceRequest) -> PaymentResponse:
        """
        Create a multi-currency invoice.

        Args:
            invoice_data: Invoice creation parameters

        Returns:
            PaymentResponse with invoice details
        """
        data = {
            'amount': invoice_data.amount,
            'amountCurrency': invoice_data.amount_currency,
        }

        if invoice_data.order_id:
            data['orderId'] = invoice_data.order_id
        if invoice_data.description:
            data['description'] = invoice_data.description
        if invoice_data.callback_url:
            data['callbackUrl'] = invoice_data.callback_url
        if invoice_data.expiration_minutes:
            data['expirationMinutes'] = invoice_data.expiration_minutes

        response_data = self._request('POST', '/v1/create_invoice', data)
        return PaymentResponse(success=True, data=response_data.get('data', {}))

    def create_payment(self, payment_data: PaymentRequest) -> PaymentResponse:
        """
        Create a fixed currency payment.

        Args:
            payment_data: Payment creation parameters

        Returns:
            PaymentResponse with payment details
        """
        data = {
            'amount': payment_data.amount,
            'amountCurrency': payment_data.amount_currency,
            'currency': payment_data.currency,
        }

        if payment_data.order_id:
            data['orderId'] = payment_data.order_id
        if payment_data.description:
            data['description'] = payment_data.description
        if payment_data.callback_url:
            data['callbackUrl'] = payment_data.callback_url
        if payment_data.expiration_minutes:
            data['expirationMinutes'] = payment_data.expiration_minutes

        response_data = self._request('POST', '/v1/create_payment', data)
        return PaymentResponse(success=True, data=response_data.get('data', {}))

    def get_payment_status(self, payment_id: str) -> PaymentStatusResponse:
        """
        Get payment status (public endpoint, no API key required).

        Args:
            payment_id: The payment ID to check

        Returns:
            PaymentStatusResponse with current status
        """
        response_data = self._request(
            'GET',
            f'/v1/invoice/{payment_id}/status',
            requires_auth=False
        )

        return PaymentStatusResponse(
            status=response_data.get('status', 'UNKNOWN'),
            current_balance=response_data.get('currentBalance', '0'),
            confirmations=response_data.get('confirmations', 0)
        )

    # ==================== BALANCE METHODS ====================

    def get_balances(self) -> BalanceResponse:
        """
        Get all currency balances.

        Returns:
            BalanceResponse with all balances
        """
        response_data = self._request('GET', '/v1/merchant/balance')
        return BalanceResponse(success=True, data=response_data.get('data', {}))

    def get_balance(self, currency: str) -> BalanceResponse:
        """
        Get specific currency balance.

        Args:
            currency: Currency code (BTC, ETH, USDT_BEP20, etc.)

        Returns:
            BalanceResponse with currency balance
        """
        response_data = self._request('GET', f'/v1/merchant/balance/{currency}')
        return BalanceResponse(success=True, data=response_data.get('data', {}))

    # ==================== WITHDRAWAL METHODS ====================

    def create_withdrawal(self, withdrawal_data: WithdrawalRequest) -> WithdrawalResponse:
        """
        Create a withdrawal to external wallet.
        Requires a withdrawal API key — pass it via SDKConfig.withdrawal_api_key
        or generate one from Dashboard → Settings → Withdrawal API Key.

        Args:
            withdrawal_data: Withdrawal parameters

        Returns:
            WithdrawalResponse with withdrawal details

        Raises:
            PaymentSDKError: If withdrawal API key is not configured
        """
        if not self.withdrawal_api_key:
            raise PaymentSDKError(
                "Withdrawal API key is required. Pass withdrawal_api_key in SDKConfig "
                "or generate one from Dashboard → Settings → Withdrawal API Key.",
                403
            )

        data = {
            'amount': withdrawal_data.amount,
            'currency': withdrawal_data.currency,
            'destinationAddress': withdrawal_data.destination_address,
        }

        if withdrawal_data.description:
            data['description'] = withdrawal_data.description

        response_data = self._request(
            'POST', '/v1/merchant/withdrawal/create', data,
            extra_headers={'X-Withdrawal-Key': self.withdrawal_api_key}
        )
        return WithdrawalResponse(success=True, data=response_data.get('data', {}))

    def get_withdrawal(self, withdrawal_id: str) -> WithdrawalResponse:
        """
        Get withdrawal status.

        Args:
            withdrawal_id: The withdrawal ID to check

        Returns:
            WithdrawalResponse with withdrawal details
        """
        response_data = self._request('GET', f'/v1/withdrawal/{withdrawal_id}')
        return WithdrawalResponse(success=True, data=response_data.get('data', {}))

    # ==================== STORE METHODS ====================

    def create_store(self, store_data: CreateStoreRequest) -> StoreResponse:
        """Create a new store."""
        data = {'name': store_data.name}
        if store_data.description:
            data['description'] = store_data.description
        if store_data.settings:
            data['settings'] = store_data.settings
        response_data = self._request('POST', '/v1/store/manage', data)
        return StoreResponse(success=True, data=response_data.get('data', {}))

    def get_store(self) -> StoreResponse:
        """Get your store details."""
        response_data = self._request('GET', '/v1/store/manage')
        return StoreResponse(success=True, data=response_data.get('data', {}))

    def update_store(self, store_data: UpdateStoreRequest) -> StoreResponse:
        """Update store settings."""
        data = {}
        if store_data.name is not None:
            data['name'] = store_data.name
        if store_data.description is not None:
            data['description'] = store_data.description
        if store_data.logo is not None:
            data['logo'] = store_data.logo
        if store_data.banner is not None:
            data['banner'] = store_data.banner
        if store_data.is_published is not None:
            data['isPublished'] = store_data.is_published
        if store_data.settings is not None:
            data['settings'] = store_data.settings
        response_data = self._request('PUT', '/v1/store/manage', data)
        return StoreResponse(success=True, data=response_data.get('data', {}))

    def create_product(self, product_data: CreateProductRequest) -> ProductResponse:
        """Add a product to your store."""
        data = {
            'name': product_data.name,
            'price': product_data.price,
            'currency': product_data.currency,
        }
        if product_data.description:
            data['description'] = product_data.description
        if product_data.images:
            data['images'] = product_data.images
        if product_data.stock is not None:
            data['stock'] = product_data.stock
        if product_data.digital_content:
            data['digitalContent'] = product_data.digital_content
        if product_data.content_type:
            data['contentType'] = product_data.content_type
        if product_data.skill_content:
            data['skillContent'] = product_data.skill_content
        response_data = self._request('POST', '/v1/store/manage/products', data)
        return ProductResponse(success=True, data=response_data.get('data', {}))

    def list_products(self, page: int = 1, limit: int = 20,
                      search: Optional[str] = None) -> ProductListResponse:
        """List products in your store."""
        params = f'?page={page}&limit={limit}'
        if search:
            params += f'&search={search}'
        response_data = self._request('GET', f'/v1/store/manage/products{params}')
        return ProductListResponse(success=True, data=response_data.get('data', {}))

    def update_product(self, product_id: str,
                       product_data: UpdateProductRequest) -> ProductResponse:
        """Update a product."""
        data = {}
        if product_data.name is not None:
            data['name'] = product_data.name
        if product_data.description is not None:
            data['description'] = product_data.description
        if product_data.price is not None:
            data['price'] = product_data.price
        if product_data.currency is not None:
            data['currency'] = product_data.currency
        if product_data.images is not None:
            data['images'] = product_data.images
        if product_data.stock is not None:
            data['stock'] = product_data.stock
        if product_data.is_active is not None:
            data['isActive'] = product_data.is_active
        if product_data.digital_content is not None:
            data['digitalContent'] = product_data.digital_content
        if product_data.content_type is not None:
            data['contentType'] = product_data.content_type
        if product_data.skill_content is not None:
            data['skillContent'] = product_data.skill_content
        response_data = self._request('PUT', f'/v1/store/manage/products/{product_id}', data)
        return ProductResponse(success=True, data=response_data.get('data', {}))

    def delete_product(self, product_id: str) -> Dict[str, Any]:
        """Delete (deactivate) a product."""
        return self._request('DELETE', f'/v1/store/manage/products/{product_id}')

    # ==================== KEY POOL METHODS ====================

    def add_product_keys(self, product_id: str, keys: List[str],
                         label: Optional[str] = None) -> Dict[str, Any]:
        """
        Add unique keys/codes to a product's key pool.
        Each customer gets one unique key on purchase (FIFO).

        Args:
            product_id: Product ID
            keys: List of unique key strings (max 10,000)
            label: Optional batch label

        Returns:
            Dict with 'added' and 'duplicates' counts
        """
        data = {'keys': keys}
        if label:
            data['label'] = label
        return self._request('POST', f'/v1/store/manage/products/{product_id}/keys', data)

    def list_product_keys(self, product_id: str, page: int = 1,
                          limit: int = 50, status: Optional[str] = None) -> Dict[str, Any]:
        """
        List keys in a product's key pool.

        Args:
            product_id: Product ID
            page: Page number
            limit: Items per page (1-100)
            status: Filter by status (AVAILABLE, ASSIGNED, REVOKED)

        Returns:
            Dict with 'keys' array and 'pagination'
        """
        params = f'?page={page}&limit={limit}'
        if status:
            params += f'&status={status}'
        return self._request('GET', f'/v1/store/manage/products/{product_id}/keys{params}')

    def get_key_pool_stats(self, product_id: str) -> Dict[str, Any]:
        """
        Get key pool statistics for a product.

        Args:
            product_id: Product ID

        Returns:
            Dict with available, assigned, revoked, total counts
        """
        return self._request('GET', f'/v1/store/manage/products/{product_id}/keys/stats')

    def remove_available_keys(self, product_id: str) -> Dict[str, Any]:
        """
        Remove all available (unassigned) keys from a product's pool.

        Args:
            product_id: Product ID

        Returns:
            Dict with 'removed' count
        """
        return self._request('DELETE', f'/v1/store/manage/products/{product_id}/keys')

    def list_orders(self, page: int = 1, limit: int = 20,
                    status: Optional[str] = None) -> OrderListResponse:
        """List orders for your store."""
        params = f'?page={page}&limit={limit}'
        if status:
            params += f'&status={status}'
        response_data = self._request('GET', f'/v1/store/manage/orders{params}')
        return OrderListResponse(success=True, data=response_data.get('data', {}))

    def get_order(self, order_id: str) -> OrderResponse:
        """Get order details."""
        response_data = self._request('GET', f'/v1/store/manage/orders/{order_id}')
        return OrderResponse(success=True, data=response_data.get('data', {}))

    def update_order_status(self, order_id: str, status: str) -> OrderResponse:
        """Update order fulfillment status."""
        response_data = self._request(
            'PUT', f'/v1/store/manage/orders/{order_id}/status',
            {'status': status}
        )
        return OrderResponse(success=True, data=response_data.get('data', {}))

    # ==================== WEBHOOK METHODS ====================

    def configure_webhook(self, config: WebhookConfig) -> Dict[str, Any]:
        """
        Configure webhook URL for payment notifications.

        Args:
            config: Webhook configuration

        Returns:
            Configuration result
        """
        data = {'webhookUrl': config.webhook_url}
        return self._request('PUT', '/v1/merchant/webhook-url', data)

    def get_webhook_config(self) -> Dict[str, Any]:
        """Get current webhook configuration and statistics."""
        return self._request('GET', '/v1/merchant/webhook-config')

    def test_webhook(self, webhook_url: Optional[str] = None) -> Dict[str, Any]:
        """
        Test webhook configuration.

        Args:
            webhook_url: Optional custom webhook URL to test

        Returns:
            Test results
        """
        data = {'webhookUrl': webhook_url} if webhook_url else None
        return self._request('POST', '/v1/merchant/test-webhook', data)

    def get_webhook_deliveries(self, page: int = 1, limit: int = 10,
                               success: Optional[bool] = None) -> Dict[str, Any]:
        """
        Get webhook delivery history.

        Args:
            page: Page number
            limit: Items per page
            success: Filter by success status

        Returns:
            Delivery history with pagination
        """
        params = f'?page={page}&limit={limit}'
        if success is not None:
            params += f'&success={str(success).lower()}'

        return self._request('GET', f'/v1/merchant/webhook-deliveries{params}')

    def delete_webhook(self) -> Dict[str, Any]:
        """Remove webhook configuration."""
        return self._request('DELETE', '/v1/merchant/webhook-url')

    def get_webhook_stats(self, days: int = 7) -> Dict[str, Any]:
        """
        Get webhook statistics.

        Args:
            days: Number of days to include in statistics

        Returns:
            Webhook statistics
        """
        return self._request('GET', f'/v1/merchant/webhook-stats?days={days}')

    # ==================== UTILITY METHODS ====================

    def generate_payment_link(self, payment_id: str) -> str:
        """
        Generate payment link for customers.

        Args:
            payment_id: The payment ID

        Returns:
            Public payment URL
        """
        return f"{self.base_url.replace('api.', 'www.')}/invoice/{payment_id}"

    def test_connection(self) -> Dict[str, Any]:
        """
        Test API connection and authentication.

        Returns:
            Connection test results
        """
        try:
            self.get_balances()
            return {'success': True, 'message': 'API connection successful'}
        except AuthenticationError:
            return {'success': False, 'message': 'Invalid API key'}
        except PaymentSDKError as e:
            return {'success': False, 'message': f'Connection failed: {e.message}'}
        except Exception:
            return {'success': False, 'message': 'Connection failed'}

    def get_api_key_preview(self) -> str:
        """Get masked API key for logging/debugging."""
        if len(self.api_key) > 12:
            return f"{self.api_key[:8]}...{self.api_key[-4:]}"
        return "***"

    @staticmethod
    def get_version() -> str:
        """Get SDK version."""
        return "1.2.0"

    @staticmethod
    def verify_webhook_signature(payload: Union[str, dict], signature: str, secret: str) -> bool:
        """Verify webhook signature (alias for webhook.verify_webhook_signature)."""
        return verify_webhook_signature(payload, signature, secret)

    @staticmethod
    def parse_webhook_event(payload: Union[str, dict], signature: str, secret: str):
        """Parse webhook event (alias for webhook.parse_webhook_event)."""
        return parse_webhook_event(payload, signature, secret)

    @staticmethod
    def get_supported_webhook_events() -> List[str]:
        """Get supported webhook events."""
        return [
            "payment.created",
            "payment.pending",
            "payment.confirmed",
            "payment.completed",
            "payment.underpaid",
            "payment.expired",
            "payment.failed",
            "payment.test",
            "withdrawal.completed",
            "withdrawal.failed",
            "withdrawal.processing",
        ]