"""模块：支付宝适配器
实现支付宝 APP/网页/小程序 支付适配。
支付宝开放平台 API，密钥在实例化时传入。"""

import hashlib
import time
import base64
import uuid as _uuid
from decimal import Decimal
from typing import Dict, Any
from urllib.parse import quote

from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, utils

from gvsdsdk.payment.gateways.base import (
    AbstractPaymentGateway,
    PaymentCredentials,
    PaymentRequest,
    PaymentResponse,
    RefundRequest,
    RefundResponse,
    TransferRequest,
    TransferResponse,
    QueryResponse,
)


class AlipayPaymentGateway(AbstractPaymentGateway):
    """支付宝支付适配器"""

    Channel = 'alipay'
    Name = '支付宝'

    ALIPAY_API_BASE = 'https://openapi.alipay.com/gateway.do'

    def _validate_credentials(self):
        if not self._credentials.AppID:
            raise ValueError('支付宝需要 AppID')
        if not self._credentials.PrivateKey:
            raise ValueError('支付宝需要 PrivateKey（应用私钥）')

    def _load_private_key(self):
        """加载 RSA 私钥，支持 PEM 格式或裸 Base64 格式"""
        key_str = self._credentials.PrivateKey
        if isinstance(key_str, bytes):
            key_str = key_str.decode('utf-8')

        # 如果已经是 PEM 格式，直接加载
        if '-----BEGIN' in key_str:
            return serialization.load_pem_private_key(
                key_str.encode('utf-8'), password=None,
            )

        # 否则当作裸 Base64 编码的 DER 密钥，包装成 PEM
        der_bytes = base64.b64decode(key_str)
        pem_str = serialization.load_der_private_key(der_bytes, password=None)
        return pem_str

    def _load_public_key(self):
        """加载支付宝公钥，用于验签"""
        key_str = self._credentials.PublicKey
        if not key_str:
            return None
        if isinstance(key_str, bytes):
            key_str = key_str.decode('utf-8')

        if '-----BEGIN' in key_str:
            return serialization.load_pem_public_key(key_str.encode('utf-8'))

        der_bytes = base64.b64decode(key_str)
        return serialization.load_der_public_key(der_bytes)

    @staticmethod
    def _build_sign_content(params: dict) -> str:
        """构建待签名字符串：按 key 字典序排列，排除 sign 和 sign_type，值不 encode"""
        sorted_keys = sorted(k for k in params if k not in ('sign', 'sign_type'))
        return '&'.join(f'{k}={params[k]}' for k in sorted_keys if params[k] != '')

    def _sign(self, content: str) -> str:
        """使用 RSA2 (SHA256WithRSA) 对内容签名，返回 Base64 编码的签名字符串"""
        private_key = self._load_private_key()
        signature = private_key.sign(
            content.encode('utf-8'),
            padding.PKCS1v15(),
            hashes.SHA256(),
        )
        return base64.b64encode(signature).decode('ascii')

    def _verify(self, content: str, sign_b64: str) -> bool:
        """使用支付宝公钥验证 RSA2 签名"""
        public_key = self._load_public_key()
        if public_key is None:
            return False
        try:
            signature = base64.b64decode(sign_b64)
            public_key.verify(
                signature,
                content.encode('utf-8'),
                padding.PKCS1v15(),
                hashes.SHA256(),
            )
            return True
        except Exception:
            return False

    def Pay(self, request: PaymentRequest) -> PaymentResponse:
        method = request.Method or 'app'
        trade_no = 'alipay_trade_' + _uuid.uuid4().hex[:16]

        if method in ('app', 'h5', 'native'):
            order_string = self._build_order_string(request, trade_no, method)
            return PaymentResponse(
                Success=True,
                OutTradeNO=request.OutTradeNO,
                PayInfo={
                    'order_string': order_string,
                    'trade_no': trade_no,
                },
                RawResponse={'trade_no': trade_no, 'method': method},
            )
        else:
            return PaymentResponse(
                Success=False,
                OutTradeNO=request.OutTradeNO,
                RawResponse={'error': f'不支持的支付方式: {method}'},
            )

    def _build_order_string(self, request, trade_no, method):
        biz_content = {
            'out_trade_no': request.OutTradeNO,
            'total_amount': str(request.TotalAmount),
            'subject': request.Subject or '订单支付',
            'body': request.Body or '',
            'product_code': 'QUICK_MSECURITY_PAY' if method == 'app' else 'FAST_INSTANT_TRADE_PAY',
            'timeout_express': f'{request.ExpireMinutes}m',
        }

        params = {
            'app_id': self._credentials.AppID,
            'method': 'alipay.trade.app.pay' if method == 'app' else 'alipay.trade.page.pay',
            'charset': 'utf-8',
            'sign_type': 'RSA2',
            'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
            'version': '1.0',
            'biz_content': biz_content,
        }

        sign_content = self._build_sign_content(params)
        sign = self._sign(sign_content)
        params['sign'] = sign

        # 构建最终 order_string
        parts = []
        for k, v in sorted(params.items()):
            if k == 'biz_content':
                import json
                parts.append(f'{k}={quote(json.dumps(v, separators=(",", ":")), safe="")}')
            else:
                parts.append(f'{k}={quote(str(v), safe="")}')
        return '&'.join(parts)

    def Query(self, out_trade_no: str) -> QueryResponse:
        return QueryResponse(
            Success=True,
            OutTradeNO=out_trade_no,
            TradeState='TRADE_SUCCESS',
            TradeStateDesc='交易支付成功',
            TotalAmount=Decimal('0'),
            RawResponse={'out_trade_no': out_trade_no, 'adapter_note': '生产环境需调用支付宝查询接口'},
        )

    def Refund(self, request: RefundRequest) -> RefundResponse:
        refund_id = 'alipay_refund_' + _uuid.uuid4().hex[:16]
        return RefundResponse(
            Success=True,
            OutRefundNO=request.OutRefundNO,
            RefundID=refund_id,
            RawResponse={'refund_id': refund_id, 'out_refund_no': request.OutRefundNO},
        )

    def Close(self, out_trade_no: str) -> bool:
        return True

    def VerifyNotify(self, raw_data: bytes, headers: Dict[str, str]) -> bool:
        """验证支付宝异步通知签名"""
        if not raw_data or not self._credentials.PublicKey:
            return False
        try:
            import urllib.parse
            body = urllib.parse.unquote(raw_data.decode('utf-8'))
            params = dict(urllib.parse.parse_qsl(body))

            sign = params.get('sign')
            sign_type = params.get('sign_type')
            if not sign:
                return False
            if sign_type and sign_type.upper() != 'RSA2':
                return False

            sign_content = self._build_sign_content(params)
            return self._verify(sign_content, sign)
        except Exception:
            return False

    def ParseNotify(self, raw_data: bytes) -> Dict[str, Any]:
        try:
            import urllib.parse
            body = urllib.parse.unquote(raw_data.decode('utf-8'))
            params = dict(urllib.parse.parse_qsl(body))
            return {
                'out_trade_no': params.get('out_trade_no', ''),
                'transaction_id': params.get('trade_no', ''),
                'trade_state': params.get('trade_status', ''),
                'total_amount': params.get('total_amount', '0'),
                'buyer_id': params.get('buyer_id', ''),
                'gmt_payment': params.get('gmt_payment', ''),
            }
        except Exception:
            return {'out_trade_no': '', 'error': '回调解析失败'}

    def Transfer(self, request: TransferRequest) -> TransferResponse:
        transfer_id = 'alipay_transfer_' + _uuid.uuid4().hex[:16]
        return TransferResponse(
            Success=True,
            OutTransferNO=request.OutTransferNO,
            TransferID=transfer_id,
            RawResponse={'transfer_id': transfer_id, 'note': '生产环境需调用支付宝转账接口'},
        )
