"""
PayPal payment integration
"""
import logging
import base64
import time
import httpx
from decimal import Decimal
from typing import Optional, Dict, Any

logger = logging.getLogger(__name__)


class PayPalPaymentHandler:
    """Handle PayPal payments with async operations"""
    
    def __init__(self, db_manager, config: dict):
        self.db = db_manager
        self.config = config
        self.http_client = httpx.AsyncClient(timeout=30.0)
        
        # Load PayPal configuration from admin_settings
        gateways = self.db.get_payment_gateway_settings()
        paypal_config = gateways.get('paypal', {})
        
        if paypal_config.get('enabled'):
            self.client_id = paypal_config.get('client_id')
            self.client_secret = paypal_config.get('client_secret')
            self.webhook_secret = paypal_config.get('webhook_secret')
            self.sandbox = paypal_config.get('sandbox', False)
            
            # Set API base URL
            if self.sandbox:
                self.base_url = 'https://api-m.sandbox.paypal.com'
            else:
                self.base_url = 'https://api-m.paypal.com'
        else:
            self.client_id = None
            self.client_secret = None
            self.base_url = None
    
    async def get_access_token(self) -> str:
        """Get PayPal OAuth access token"""
        auth = base64.b64encode(
            f"{self.client_id}:{self.client_secret}".encode()
        ).decode()
        
        response = await self.http_client.post(
            f"{self.base_url}/v1/oauth2/token",
            headers={
                'Authorization': f'Basic {auth}',
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            data={'grant_type': 'client_credentials'}
        )
        
        if response.status_code == 200:
            data = response.json()
            return data['access_token']
        else:
            raise Exception(f"Failed to get PayPal access token: {response.text}")
    
    async def create_setup_token(self, return_url: str, cancel_url: str) -> dict:
        """Create PayPal Vault Setup Token for payment method saving"""
        try:
            access_token = await self.get_access_token()
            
            response = await self.http_client.post(
                f"{self.base_url}/v3/vault/setup-tokens",
                headers={
                    'Authorization': f'Bearer {access_token}',
                    'Content-Type': 'application/json'
                },
                json={
                    'payment_source': {
                        'paypal': {
                            'usage_type': 'MERCHANT',
                            'experience_context': {
                                'return_url': return_url,
                                'cancel_url': cancel_url,
                                'shipping_preference': 'NO_SHIPPING',
                                'user_action': 'CONTINUE'
                            }
                        }
                    }
                }
            )
            
            if response.status_code == 201:
                data = response.json()
                return {
                    'success': True,
                    'id': data['id'],
                    'approval_url': data['links'][1]['href']
                }
            else:
                error_data = response.json() if response.headers.get('Content-Type', '').startswith('application/json') else {}
                error_details = error_data.get('details', [])
                
                # Check for specific known errors
                for detail in error_details:
                    issue = detail.get('issue')
                    if issue == 'NOT_ENABLED_TO_VAULT_PAYMENT_SOURCE':
                        logger.error(f"PayPal account does not have Vault v3 Payment Tokens enabled: {response.text}")
                        # Fall back to legacy billing agreement flow
                        logger.info("Falling back to legacy v1 billing agreement flow")
                        return await self.create_billing_agreement(None, return_url, cancel_url)
                
                logger.error(f"Failed to create setup token: {response.text}")
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            logger.error(f"Error creating setup token: {e}")
            return {'success': False, 'error': str(e)}
    
    async def create_payment_token(self, setup_token_id: str) -> dict:
        """Exchange setup token for permanent payment token"""
        try:
            access_token = await self.get_access_token()
            
            response = await self.http_client.post(
                f"{self.base_url}/v3/vault/payment-tokens",
                headers={
                    'Authorization': f'Bearer {access_token}',
                    'Content-Type': 'application/json'
                },
                json={
                    'setup_token_id': setup_token_id
                }
            )
            
            if response.status_code == 201:
                data = response.json()
                return {
                    'success': True,
                    'payment_token_id': data['id'],
                    'payer_email': data.get('payer', {}).get('email_address'),
                    'payment_method_type': data['payment_source']['paypal']['card_type'] if 'card_type' in data['payment_source']['paypal'] else 'PAYPAL'
                }
            else:
                logger.error(f"Failed to create payment token: {response.text}")
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            logger.error(f"Error creating payment token: {e}")
            return {'success': False, 'error': str(e)}
    
    async def charge_payment_token(self, payment_token_id: str, amount: float, currency_code: str = 'USD') -> dict:
        """Charge saved payment token (off-session merchant initiated transaction)"""
        try:
            access_token = await self.get_access_token()
            
            response = await self.http_client.post(
                f"{self.base_url}/v2/checkout/orders",
                headers={
                    'Authorization': f'Bearer {access_token}',
                    'Content-Type': 'application/json',
                    'PayPal-Request-Id': f'charge_{payment_token_id}_{int(time.time())}'
                },
                json={
                    'intent': 'CAPTURE',
                    'purchase_units': [{
                        'amount': {
                            'currency_code': currency_code,
                            'value': f"{amount:.2f}"
                        }
                    }],
                    'payment_source': {
                        'token': {
                            'id': payment_token_id,
                            'type': 'PAYMENT_METHOD_TOKEN'
                        }
                    },
                    'payment_instruction': {
                        'usage': 'MERCHANT',
                        'customer_present': False
                    }
                }
            )
            
            if response.status_code == 201:
                data = response.json()
                return {
                    'success': True,
                    'order_id': data['id'],
                    'status': data['status']
                }
            else:
                logger.error(f"Failed to charge payment token: {response.text}")
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            logger.error(f"Error charging payment token: {e}")
            return {'success': False, 'error': str(e)}
    
    async def create_billing_agreement(self, user_id: int, return_url: str, 
                                      cancel_url: str) -> dict:
        """Create PayPal billing agreement for recurring payments"""
        try:
            access_token = await self.get_access_token()
            
            # Create billing agreement token
            response = await self.http_client.post(
                f"{self.base_url}/v1/billing-agreements/agreement-tokens",
                headers={
                    'Authorization': f'Bearer {access_token}',
                    'Content-Type': 'application/json'
                },
                json={
                    'description': 'AISBF Subscription',
                    'payer': {
                        'payment_method': 'PAYPAL'
                    },
                    'plan': {
                        'type': 'MERCHANT_INITIATED_BILLING',
                        'merchant_preferences': {
                            'return_url': return_url,
                            'cancel_url': cancel_url,
                            'notify_url': f"{self.config['base_url']}/api/webhooks/paypal",
                            'accepted_payment_type': 'INSTANT',
                            'skip_shipping_address': True
                        }
                    }
                }
            )
            
            if response.status_code == 201:
                data = response.json()
                token = data['token_id']
                
                # Generate approval URL
                approval_url = f"{self.base_url}/checkoutnow?token={token}"
                
                return {
                    'success': True,
                    'token': token,
                    'approval_url': approval_url
                }
            else:
                error_data = response.json() if response.headers.get('Content-Type', '').startswith('application/json') else {}
                error_details = error_data.get('details', [])
                
                # Check for specific known errors
                for detail in error_details:
                    error_name = detail.get('name')
                    if error_name == 'REFUSED_MARK_REF_TXN_NOT_ENABLED':
                        logger.error(f"PayPal account does not have Reference Transactions enabled: {response.text}")
                        return {
                            'success': False,
                            'error': 'PayPal merchant account is not configured for Reference Transactions. Please contact PayPal support to enable this feature for your account.',
                            'error_code': 'reference_transactions_not_enabled'
                        }
                
                logger.error(f"Failed to create billing agreement: {response.text}")
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            logger.error(f"Error creating PayPal billing agreement: {e}")
            return {'success': False, 'error': str(e)}
    
    async def execute_billing_agreement(self, user_id: int, token: str) -> dict:
        """Execute billing agreement after user approval"""
        try:
            access_token = await self.get_access_token()
            
            # Execute agreement
            response = await self.http_client.post(
                f"{self.base_url}/v1/billing-agreements/agreements",
                headers={
                    'Authorization': f'Bearer {access_token}',
                    'Content-Type': 'application/json'
                },
                json={'token_id': token}
            )
            
            if response.status_code == 201:
                data = response.json()
                agreement_id = data['id']
                
                # Store payment method
                with self.db._get_connection() as conn:
                    cursor = conn.cursor()
                    ph = self.db.placeholder
                    cursor.execute(f"""
                        INSERT INTO payment_methods
                        (user_id, type, identifier, is_default, is_active)
                        VALUES ({ph}, 'paypal', {ph}, 1, 1)
                    """, (user_id, agreement_id))
                    conn.commit()
                
                logger.info(f"Executed PayPal billing agreement for user {user_id}")
                
                return {
                    'success': True,
                    'agreement_id': agreement_id,
                    'payer_email': data.get('payer', {}).get('payer_info', {}).get('email')
                }
            else:
                logger.error(f"Failed to execute billing agreement: {response.text}")
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            logger.error(f"Error executing PayPal billing agreement: {e}")
            return {'success': False, 'error': str(e)}
    
    async def handle_webhook(self, payload: dict, headers: dict) -> dict:
        """Handle PayPal webhook events"""
        try:
            # Verify webhook signature for security
            if not await self._verify_webhook_signature(payload, headers):
                logger.error("PayPal webhook signature verification failed")
                return {'status': 'error', 'message': 'Invalid signature'}
            
            event_type = payload.get('event_type')
            resource = payload.get('resource', {})
            
            logger.info(f"Received PayPal webhook: {event_type}")
            
            # Vault v3 / Payment events
            if event_type == 'CHECKOUT.ORDER.COMPLETED':
                await self._handle_order_completed(resource)
            elif event_type == 'CHECKOUT.ORDER.APPROVED':
                await self._handle_order_approved(resource)
            elif event_type == 'PAYMENT.CAPTURE.COMPLETED':
                await self._handle_payment_capture_completed(resource)
            elif event_type == 'PAYMENT.CAPTURE.DENIED':
                await self._handle_payment_capture_denied(resource)
            elif event_type == 'PAYMENT.CAPTURE.REFUNDED':
                await self._handle_payment_refunded(resource)
            
            # Legacy billing agreement events (backward compatibility)
            elif event_type == 'PAYMENT.SALE.COMPLETED':
                await self._handle_payment_completed(resource)
            elif event_type == 'PAYMENT.SALE.DENIED':
                await self._handle_payment_denied(resource)
            
            # Vault token events
            elif event_type == 'VAULT.PAYMENT-TOKEN.CREATED':
                await self._handle_vault_token_created(resource)
            elif event_type == 'VAULT.PAYMENT-TOKEN.DELETED':
                await self._handle_vault_token_deleted(resource)
            
            # Customer disputes
            elif event_type == 'CUSTOMER.DISPUTE.CREATED':
                await self._handle_dispute_created(resource)
            elif event_type == 'CUSTOMER.DISPUTE.RESOLVED':
                await self._handle_dispute_resolved(resource)
            
            else:
                logger.info(f"Unhandled PayPal webhook event: {event_type}")
            
            return {'status': 'success'}
            
        except Exception as e:
            logger.error(f"Error handling PayPal webhook: {e}")
            return {'status': 'error', 'message': str(e)}
    
    async def _verify_webhook_signature(self, payload: dict, headers: dict) -> bool:
        """Verify PayPal webhook signature via PayPal's verify-webhook-signature API."""
        webhook_id = self.webhook_secret  # stored as 'webhook_secret' in admin settings
        if not webhook_id:
            logger.error("PayPal webhook_id not configured - rejecting webhook (configure webhook_id in payment settings)")
            return False

        try:
            access_token = await self.get_access_token()
            body = {
                "auth_algo":         headers.get("paypal-auth-algo", ""),
                "cert_url":          headers.get("paypal-cert-url", ""),
                "transmission_id":   headers.get("paypal-transmission-id", ""),
                "transmission_sig":  headers.get("paypal-transmission-sig", ""),
                "transmission_time": headers.get("paypal-transmission-time", ""),
                "webhook_id":        webhook_id,
                "webhook_event":     payload,
            }
            response = await self.http_client.post(
                f"{self.base_url}/v1/notifications/verify-webhook-signature",
                headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"},
                json=body,
            )
            result = response.json()
            verified = result.get("verification_status") == "SUCCESS"
            if not verified:
                logger.warning(f"PayPal webhook verification failed: {result}")
            return verified
        except Exception as e:
            logger.error(f"PayPal webhook signature verification error: {e}")
            return False
    
    async def _credit_wallet_for_paypal(self, user_id: int, amount: Decimal,
                                         gateway_tx_id: str, description: str) -> None:
        """Credit the user wallet via WalletManager using the db-compatible interface."""
        from aisbf.payments.wallet.manager import WalletManager
        wallet_manager = WalletManager(self.db)
        await wallet_manager.credit_wallet(
            user_id=user_id,
            amount=amount,
            transaction_details={
                'payment_gateway': 'paypal',
                'gateway_transaction_id': gateway_tx_id,
                'description': description,
                'metadata': {'paypal_tx_id': gateway_tx_id},
            }
        )
        logger.info(f"PayPal wallet credit: user={user_id}, amount={amount}, tx={gateway_tx_id}")

    async def _handle_order_completed(self, resource: dict):
        """Handle completed checkout order — credit wallet for top-up orders."""
        order_id = resource.get('id')
        logger.info(f"PayPal order completed: {order_id}")

        purchase_units = resource.get('purchase_units', [])
        if not purchase_units:
            return
        pu = purchase_units[0]
        description = pu.get('description', '')
        if 'Wallet top up' not in description:
            return
        try:
            amount = Decimal(pu['amount']['value'])
            user_id = int(resource.get('custom_id', 0))
        except (KeyError, ValueError, TypeError) as e:
            logger.error(f"PayPal order completed: could not parse amount/user_id: {e}")
            return
        if user_id <= 0:
            logger.error(f"PayPal order completed: missing custom_id on order {order_id}")
            return
        await self._credit_wallet_for_paypal(user_id, amount, order_id,
                                              'Wallet top up via PayPal')

    async def _handle_order_approved(self, resource: dict):
        """Handle approved order — record pending capture state."""
        order_id = resource.get('id')
        logger.info(f"PayPal order approved (awaiting capture): {order_id}")
        try:
            placeholder = '?' if self.db.db_type == 'sqlite' else '%s'
            with self.db._get_connection() as conn:
                cursor = conn.cursor()
                cursor.execute(f"""
                    INSERT OR IGNORE INTO payment_transactions
                        (gateway, gateway_transaction_id, status, created_at)
                    VALUES ({placeholder}, {placeholder}, 'pending_capture', CURRENT_TIMESTAMP)
                    ON CONFLICT(gateway_transaction_id) DO UPDATE SET status='pending_capture'
                """, ('paypal', order_id)) if self.db.db_type == 'sqlite' else cursor.execute(f"""
                    INSERT INTO payment_transactions
                        (gateway, gateway_transaction_id, status, created_at)
                    VALUES ({placeholder}, {placeholder}, 'pending_capture', CURRENT_TIMESTAMP)
                    ON DUPLICATE KEY UPDATE status='pending_capture'
                """, ('paypal', order_id))
                conn.commit()
        except Exception as e:
            logger.warning(f"PayPal: could not record approved order {order_id}: {e}")

    async def _handle_payment_capture_completed(self, resource: dict):
        """Handle completed payment capture — credit wallet."""
        capture_id = resource.get('id')
        logger.info(f"PayPal payment capture completed: {capture_id}")
        custom_id = resource.get('custom_id', '')
        amount_obj = resource.get('amount', {})
        try:
            amount = Decimal(amount_obj.get('value', '0'))
            user_id = int(custom_id) if custom_id else 0
        except (ValueError, TypeError):
            user_id = 0
        if user_id > 0 and amount > 0:
            await self._credit_wallet_for_paypal(user_id, amount, capture_id,
                                                  'Payment capture via PayPal')
        else:
            logger.warning(f"PayPal capture completed but missing user_id/amount: {capture_id}")

    async def _handle_payment_capture_denied(self, resource: dict):
        """Handle denied payment capture — queue for retry."""
        capture_id = resource.get('id')
        logger.warning(f"PayPal payment capture denied: {capture_id}")
        # Record failed attempt in the payment_retry_queue so the scheduler retries it
        try:
            placeholder = '?' if self.db.db_type == 'sqlite' else '%s'
            with self.db._get_connection() as conn:
                cursor = conn.cursor()
                cursor.execute(f"""
                    INSERT INTO payment_retry_queue
                        (gateway, gateway_transaction_id, status, next_retry_at, created_at)
                    VALUES ({placeholder}, {placeholder}, 'pending',
                            CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
                """, ('paypal', capture_id))
                conn.commit()
        except Exception as e:
            logger.error(f"PayPal: failed to queue denied capture {capture_id} for retry: {e}")

    async def _handle_payment_refunded(self, resource: dict):
        """Handle refunded payment — debit the wallet to reverse the credit."""
        refund_id = resource.get('id')
        logger.info(f"PayPal payment refunded: {refund_id}")
        amount_obj = resource.get('amount', {})
        custom_id = resource.get('custom_id', '')
        try:
            amount = Decimal(amount_obj.get('value', '0'))
            user_id = int(custom_id) if custom_id else 0
        except (ValueError, TypeError):
            user_id = 0
        if user_id > 0 and amount > 0:
            try:
                from aisbf.payments.wallet.manager import WalletManager
                wallet_manager = WalletManager(self.db)
                await wallet_manager.debit_wallet(
                    user_id=user_id,
                    amount=amount,
                    transaction_details={
                        'payment_gateway': 'paypal',
                        'gateway_transaction_id': refund_id,
                        'description': 'Refund via PayPal',
                        'metadata': {'refund_id': refund_id},
                    }
                )
                logger.info(f"PayPal refund applied: user={user_id}, amount={amount}")
            except Exception as e:
                logger.error(f"PayPal refund: wallet debit failed for {refund_id}: {e}")
        else:
            logger.warning(f"PayPal refund: cannot apply refund {refund_id} — missing user_id/amount")

    async def _handle_vault_token_created(self, resource: dict):
        """Handle vault token creation — store as a payment method."""
        token_id = resource.get('id')
        logger.info(f"PayPal vault token created: {token_id}")
        customer = resource.get('customer', {})
        merchant_customer_id = customer.get('merchant_customer_id') or resource.get('metadata', {}).get('merchant_customer_id')
        if not (token_id and merchant_customer_id):
            logger.warning(f"PayPal vault token {token_id}: missing merchant_customer_id, skipping save")
            return
        try:
            user_id = int(merchant_customer_id)
        except (ValueError, TypeError):
            logger.warning(f"PayPal vault token {token_id}: invalid merchant_customer_id {merchant_customer_id!r}")
            return
        try:
            self.db.add_payment_method(user_id, 'paypal', token_id, is_default=False, metadata={'paypal_vault_token': token_id})
            logger.info(f"Stored PayPal vault token {token_id} as payment method for user {user_id}")
        except Exception as e:
            logger.error(f"PayPal: failed to store vault token {token_id} for user {user_id}: {e}")

    async def _handle_vault_token_deleted(self, resource: dict):
        """Handle vault token deletion — deactivate matching payment method."""
        token_id = resource.get('id')
        logger.info(f"PayPal vault token deleted: {token_id}")
        try:
            placeholder = '?' if self.db.db_type == 'sqlite' else '%s'
            with self.db._get_connection() as conn:
                cursor = conn.cursor()
                cursor.execute(f"""
                    UPDATE payment_methods
                    SET is_active = 0
                    WHERE gateway_token = {placeholder} AND type = 'paypal'
                """, (token_id,))
                conn.commit()
                if cursor.rowcount:
                    logger.info(f"Deactivated payment method for deleted PayPal vault token {token_id}")
        except Exception as e:
            logger.error(f"PayPal: failed to deactivate payment method for vault token {token_id}: {e}")

    async def _handle_dispute_created(self, resource: dict):
        """Handle dispute creation — log and alert via application logger (admin monitors logs)."""
        dispute_id = resource.get('dispute_id')
        reason = resource.get('reason', 'unknown')
        amount_obj = (resource.get('dispute_amount') or {})
        amount = amount_obj.get('value', '?')
        logger.error(
            f"PAYPAL DISPUTE CREATED — id={dispute_id} reason={reason} amount={amount}. "
            "Review at https://www.paypal.com/disputes/ and update dispute status manually."
        )

    async def _handle_dispute_resolved(self, resource: dict):
        """Handle dispute resolution."""
        dispute_id = resource.get('dispute_id')
        outcome = resource.get('dispute_outcome', {}).get('outcome_code', 'unknown')
        logger.info(f"PayPal dispute resolved: {dispute_id} outcome={outcome}")
    
    async def create_topup_order(self, user_id: int, amount: Decimal) -> dict:
        """Create PayPal order for wallet top up"""
        try:
            access_token = await self.get_access_token()
            
            response = await self.http_client.post(
                f"{self.base_url}/v2/checkout/orders",
                headers={
                    'Authorization': f'Bearer {access_token}',
                    'Content-Type': 'application/json'
                },
                json={
                    'intent': 'CAPTURE',
                    'purchase_units': [{
                        'amount': {
                            'currency_code': self.config.get('currency_code', 'USD'),
                            'value': f"{amount:.2f}"
                        },
                        'description': f'Wallet top up: ${amount:.2f}'
                    }],
                    'application_context': {
                        'return_url': f"{self.config['base_url']}/wallet/topup/complete",
                        'cancel_url': f"{self.config['base_url']}/wallet/topup/cancel",
                        'shipping_preference': 'NO_SHIPPING'
                    }
                }
            )
            
            if response.status_code == 201:
                data = response.json()
                approval_url = next(link['href'] for link in data['links'] if link['rel'] == 'approve')
                
                logger.info(f"Created PayPal top up order for user {user_id}: {data['id']}")
                
                return {
                    'success': True,
                    'order_id': data['id'],
                    'approval_url': approval_url,
                    'amount': amount,
                    'payment_method': 'paypal'
                }
            else:
                logger.error(f"Failed to create PayPal top up order: {response.text}")
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            logger.error(f"Error creating PayPal top up order: {e}")
            return {'success': False, 'error': str(e)}

    async def _handle_payment_completed(self, resource: dict):
        """Handle completed payment (legacy PAYMENT.SALE.COMPLETED) — credit wallet if applicable."""
        payment_id = resource.get('id')
        logger.info(f"PayPal payment completed: {payment_id}")
        custom_id = resource.get('custom', '') or resource.get('custom_id', '')
        amount_obj = resource.get('amount', {})
        try:
            amount = Decimal(amount_obj.get('total', amount_obj.get('value', '0')))
            user_id = int(custom_id) if custom_id else 0
        except (ValueError, TypeError):
            user_id = 0
        if user_id > 0 and amount > 0:
            await self._credit_wallet_for_paypal(user_id, amount, payment_id, 'Payment via PayPal')
        else:
            logger.debug(f"PayPal PAYMENT.SALE.COMPLETED {payment_id}: no user_id/amount to credit")

    async def _handle_payment_denied(self, resource: dict):
        """Handle denied payment (legacy PAYMENT.SALE.DENIED) — queue for retry."""
        payment_id = resource.get('id')
        logger.warning(f"PayPal payment denied: {payment_id}")
        try:
            placeholder = '?' if self.db.db_type == 'sqlite' else '%s'
            with self.db._get_connection() as conn:
                cursor = conn.cursor()
                cursor.execute(f"""
                    INSERT INTO payment_retry_queue
                        (gateway, gateway_transaction_id, status, next_retry_at, created_at)
                    VALUES ({placeholder}, {placeholder}, 'pending',
                            CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
                """, ('paypal', payment_id))
                conn.commit()
        except Exception as e:
            logger.error(f"PayPal: failed to queue denied payment {payment_id} for retry: {e}")
