"""模块：支付管理器
SAAS 级支付编排器——子服务通过此管理器发起支付操作。

职责：
  1. 根据租户UUID + 平台查询 PaymentGatewayConfig
  2. 解密密钥构建 PaymentCredentials
  3. 从网关注册表获取适配器类，创建网关实例
  4. 执行支付操作并记录 PaymentTransaction 流水"""

import uuid
import datetime
import logging
from decimal import Decimal
from gvsdsdk.payment.gateways.base import (
    PaymentCredentials,
    PaymentRequest,
    PaymentResponse,
    RefundRequest,
    RefundResponse,
    TransferRequest,
    TransferResponse,
    QueryResponse,
)
from gvsdsdk.payment.gateways.registry import payment_registry
from gvsdsdk.payment.models import (
    PaymentGatewayConfig,
    PaymentTransaction,
    EncryptField,
)

logger = logging.getLogger(__name__)


class PaymentError(Exception):
    def __init__(self, message, transaction=None):
        super().__init__(message)
        self.Transaction = transaction


class PaymentManager:
    """支付编排器——SAAS 主服务器核心入口

    用法:
        manager = PaymentManager(tenant_uuid='xxx')

        # 配置支付网关（SAAS 管理员操作）
        manager.ConfigureGateway(
            platform='wechat',
            app_id='wx123',
            mch_id='456',
            api_key='secret',
            notify_url='https://xxx.gvsds.com/api/payment/notify/wechat/',
        )

        # 发起支付
        tx = manager.Pay(
            platform='wechat',
            amount=Decimal('100.00'),
            subject='订单支付',
            method='jsapi',
        )

        # 查询
        manager.Query(out_trade_no='PY-xxx')

        # 退款
        manager.Refund(out_trade_no='PY-xxx', amount=Decimal('100.00'))

        # 回调处理
        manager.HandleNotify(platform='wechat', raw_data=body, headers=headers)
    """

    def __init__(self, tenant_uuid):
        self.TenantUUID = tenant_uuid

    def ConfigureGateway(self, platform, app_id='', mch_id='',
                         api_key='', private_key='', public_key='',
                         cert_path='', cert_key_path='',
                         notify_url='', return_url='', extra=None, set_default=False):
        """配置支付网关——SAAS 管理员为租户配置支付渠道

        Args:
            platform: 支付平台 ('wechat', 'alipay', 'bank')
            app_id: 应用 ID
            mch_id: 商户号
            api_key: API 密钥（将加密存储）
            private_key: 私钥（将加密存储）
            public_key: 公钥（将加密存储）
            cert_path: 证书路径
            cert_key_path: 证书密钥路径
            notify_url: 回调通知 URL
            return_url: 支付完成跳转 URL
            extra: 额外配置
            set_default: 是否设为默认渠道

        Returns:
            PaymentGatewayConfig 实例
        """
        config, created = PaymentGatewayConfig.objects.update_or_create(
            TenantUUID=self.TenantUUID,
            Platform=platform,
            defaults={
                'AppID': app_id,
                'MchID': mch_id,
                'APIKeyEncrypted': EncryptField(api_key) if api_key else '',
                'PrivateKeyEncrypted': EncryptField(private_key) if private_key else '',
                'PublicKeyEncrypted': EncryptField(public_key) if public_key else '',
                'CertPath': cert_path,
                'CertKeyPath': cert_key_path,
                'NotifyURL': notify_url,
                'ReturnURL': return_url,
                'ConfigExtra': extra or {},
                'ConfigStatus': 1,
                'UpdateTime': datetime.datetime.utcnow(),
            },
        )

        if set_default:
            PaymentGatewayConfig.objects.filter(
                TenantUUID=self.TenantUUID, ConfigDefault=True,
            ).update(ConfigDefault=False)
            config.ConfigDefault = True
            config.save(update_fields=['ConfigDefault'])

        logger.info(f'支付网关配置: {platform}@tenant={self.TenantUUID}')
        return config

    def Pay(self, platform, amount, subject='', body='',
            method='jsapi', open_id='', client_ip='',
            extra=None, expire_minutes=30):
        """发起支付

        Args:
            platform: 支付平台
            amount: 金额
            subject: 商品标题
            body: 商品描述
            method: 支付方式 (jsapi/app/h5/native/miniapp)
            open_id: 用户 OpenID（JSAPI 必填）
            client_ip: 客户端 IP
            extra: 额外参数
            expire_minutes: 过期分钟数

        Returns:
            PaymentTransaction 实例

        Raises:
            PaymentError: 支付失败
        """
        config = PaymentGatewayConfig.FromTenantAndPlatform(self.TenantUUID, platform)
        if config is None:
            raise PaymentError(f'租户未配置支付渠道: {platform}')

        creds = self._build_credentials(config)
        gateway = payment_registry.create(platform, creds)

        out_trade_no = f'PY-{uuid.uuid4().hex[:16].upper()}'
        req = PaymentRequest(
            OutTradeNO=out_trade_no,
            TotalAmount=Decimal(str(amount)),
            Subject=subject,
            Body=body,
            Method=method,
            OpenID=open_id,
            ClientIP=client_ip,
            ExpireMinutes=expire_minutes,
            Extra=extra or {},
        )

        tx = PaymentTransaction(
            TransactionUUID=uuid.uuid4().bytes,
            TenantUUID=self.TenantUUID,
            Platform=platform,
            TransactionType=PaymentTransaction.TX_PAY,
            OutTradeNO=out_trade_no,
            TotalAmount=Decimal(str(amount)),
            Subject=subject,
            RawRequest={
                'method': method,
                'open_id': open_id,
                'extra': extra or {},
            },
        )
        tx.save()

        resp = gateway.Pay(req)
        tx.RawResponse = resp.RawResponse
        tx.UpdateTime = datetime.datetime.utcnow()

        if resp.Success:
            tx.ChannelTransactionID = resp.TransactionID
            tx.save(update_fields=['RawResponse', 'ChannelTransactionID', 'UpdateTime'])
            tx._prepay_info = resp.PayInfo
            return tx
        else:
            tx.MarkFailed()
            raise PaymentError('支付发起失败', transaction=tx)

    def Query(self, out_trade_no, platform=None):
        """查询订单状态

        Args:
            out_trade_no: 商户订单号
            platform: 支付平台（不传则从交易记录推断）

        Returns:
            QueryResponse
        """
        try:
            tx = PaymentTransaction.objects.get(OutTradeNO=out_trade_no)
        except PaymentTransaction.DoesNotExist:
            raise PaymentError(f'交易记录不存在: {out_trade_no}')

        platform = platform or tx.Platform
        config = PaymentGatewayConfig.FromTenantAndPlatform(self.TenantUUID, platform)
        if config is None:
            raise PaymentError(f'租户未配置支付渠道: {platform}')

        creds = self._build_credentials(config)
        gateway = payment_registry.create(platform, creds)
        result = gateway.Query(out_trade_no)

        if result.IsPaid and not tx.IsSuccess:
            tx.MarkSuccess(
                channel_transaction_id=result.TransactionID,
                notify_data={'query_result': result.RawResponse},
            )
        elif result.IsRefunded:
            tx.TransactionStatus = PaymentTransaction.TX_CLOSED
            tx.RawResponse = result.RawResponse
            tx.UpdateTime = datetime.datetime.utcnow()
            tx.save(update_fields=['TransactionStatus', 'RawResponse', 'UpdateTime'])

        return result

    def Refund(self, out_trade_no, amount, reason='', platform=None, extra=None):
        """发起退款

        Args:
            out_trade_no: 商户订单号
            amount: 退款金额
            reason: 退款原因
            platform: 支付平台
            extra: 额外参数

        Returns:
            PaymentTransaction 实例
        """
        try:
            pay_tx = PaymentTransaction.objects.get(OutTradeNO=out_trade_no, TransactionType=PaymentTransaction.TX_PAY)
        except PaymentTransaction.DoesNotExist:
            raise PaymentError(f'原始交易不存在: {out_trade_no}')

        if not pay_tx.IsSuccess:
            raise PaymentError(f'原始交易未支付成功，无法退款: {out_trade_no}')

        platform = platform or pay_tx.Platform
        config = PaymentGatewayConfig.FromTenantAndPlatform(self.TenantUUID, platform)
        if config is None:
            raise PaymentError(f'租户未配置支付渠道: {platform}')

        creds = self._build_credentials(config)
        gateway = payment_registry.create(platform, creds)

        out_refund_no = f'RF-{uuid.uuid4().hex[:16].upper()}'
        amount = Decimal(str(amount))

        if amount > pay_tx.TotalAmount:
            raise PaymentError(f'退款金额超过原始交易金额: {amount} > {pay_tx.TotalAmount}')

        req = RefundRequest(
            OutTradeNO=out_trade_no,
            OutRefundNO=out_refund_no,
            TotalAmount=pay_tx.TotalAmount,
            RefundAmount=amount,
            Reason=reason,
            Extra=extra or {},
        )

        rx = PaymentTransaction(
            TransactionUUID=uuid.uuid4().bytes,
            TenantUUID=self.TenantUUID,
            Platform=platform,
            TransactionType=PaymentTransaction.TX_REFUND,
            OutTradeNO=out_trade_no,
            OutRefundNO=out_refund_no,
            TotalAmount=amount,
            RawRequest={'refund_reason': reason, 'extra': extra or {}},
        )
        rx.save()

        resp = gateway.Refund(req)
        rx.RawResponse = resp.RawResponse

        if resp.Success:
            rx.MarkSuccess(channel_transaction_id=resp.RefundID)
        else:
            rx.MarkFailed()

        return rx

    def Transfer(self, platform, amount, payee_account='',
                 payee_name='', description='', transfer_type='wallet',
                 extra=None):
        """企业付款到零钱/银行卡

        Args:
            platform: 支付平台
            amount: 转账金额
            payee_account: 收款账户
            payee_name: 收款人姓名
            description: 转账备注
            transfer_type: 转账类型 (wallet/bank)
            extra: 额外参数

        Returns:
            PaymentTransaction 实例
        """
        config = PaymentGatewayConfig.FromTenantAndPlatform(self.TenantUUID, platform)
        if config is None:
            raise PaymentError(f'租户未配置支付渠道: {platform}')

        creds = self._build_credentials(config)
        gateway = payment_registry.create(platform, creds)

        out_transfer_no = f'TF-{uuid.uuid4().hex[:16].upper()}'
        amount = Decimal(str(amount))

        req = TransferRequest(
            OutTransferNO=out_transfer_no,
            Amount=amount,
            PayeeAccount=payee_account,
            PayeeName=payee_name,
            Description=description,
            TransferType=transfer_type,
            Extra=extra or {},
        )

        tx = PaymentTransaction(
            TransactionUUID=uuid.uuid4().bytes,
            TenantUUID=self.TenantUUID,
            Platform=platform,
            TransactionType=PaymentTransaction.TX_TRANSFER,
            OutTradeNO=out_transfer_no,
            TotalAmount=amount,
            Subject=description,
            RawRequest={
                'payee_account': payee_account,
                'payee_name': payee_name,
                'transfer_type': transfer_type,
                'extra': extra or {},
            },
        )
        tx.save()

        try:
            resp = gateway.Transfer(req)
        except NotImplementedError as e:
            tx.MarkFailed(raw_response={'error': str(e)})
            raise PaymentError(str(e), transaction=tx)

        tx.RawResponse = resp.RawResponse
        if resp.Success:
            tx.MarkSuccess(channel_transaction_id=resp.TransferID)
        else:
            tx.MarkFailed()

        return tx

    def HandleNotify(self, platform, raw_data, headers=None):
        """处理支付回调通知

        Args:
            platform: 支付平台
            raw_data: 原始回调数据 (bytes)
            headers: 请求头字典

        Returns:
            PaymentTransaction 实例或 None
        """
        config = PaymentGatewayConfig.FromTenantAndPlatform(self.TenantUUID, platform)
        if config is None:
            logger.error(f'租户未配置支付渠道: {platform}')
            return None

        creds = self._build_credentials(config)
        gateway = payment_registry.create(platform, creds)

        if not gateway.VerifyNotify(raw_data, headers or {}):
            logger.warning(f'支付回调签名验证失败: {platform}')
            return None

        notify_data = gateway.ParseNotify(raw_data)
        out_trade_no = notify_data.get('out_trade_no', '')
        if not out_trade_no:
            logger.warning('回调缺少 out_trade_no')
            return None

        try:
            tx = PaymentTransaction.objects.get(OutTradeNO=out_trade_no)
        except PaymentTransaction.DoesNotExist:
            logger.warning(f'回调对应交易不存在: {out_trade_no}')
            return None

        trade_state = notify_data.get('trade_state', '')
        transaction_id = notify_data.get('transaction_id', '')

        if trade_state in ('SUCCESS', 'TRADE_SUCCESS', 'TRADE_FINISHED'):
            tx.MarkSuccess(
                channel_transaction_id=transaction_id,
                notify_data=notify_data,
            )
        elif trade_state in ('CLOSED', 'REVOKED', 'TRADE_CLOSED'):
            tx.MarkClosed()
        elif trade_state in ('REFUND', 'TRADE_REFUND'):
            tx.TransactionStatus = PaymentTransaction.TX_CLOSED
            tx.NotifyData = notify_data
            tx.UpdateTime = datetime.datetime.utcnow()
            tx.save(update_fields=['TransactionStatus', 'NotifyData', 'UpdateTime'])

        return tx

    def GetGatewayInstance(self, platform):
        """直接获取网关实例——供子服务自定义操作

        Args:
            platform: 支付平台

        Returns:
            AbstractPaymentGateway 实例

        Raises:
            PaymentError: 租户未配置支付渠道
        """
        config = PaymentGatewayConfig.FromTenantAndPlatform(self.TenantUUID, platform)
        if config is None:
            raise PaymentError(f'租户未配置支付渠道: {platform}')

        creds = self._build_credentials(config)
        return payment_registry.create(platform, creds)

    def _build_credentials(self, config):
        """从数据库配置构建 PaymentCredentials"""
        return PaymentCredentials(
            AppID=config.AppID,
            MchID=config.MchID,
            APIKey=config.DecryptAPIKey(),
            PrivateKey=config.DecryptPrivateKey(),
            PublicKey=config.DecryptPublicKey(),
            CertPath=config.CertPath,
            CertKeyPath=config.CertKeyPath,
            NotifyURL=config.NotifyURL,
            ReturnURL=config.ReturnURL,
            Extra=config.ConfigExtra,
        )
