package notify

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	commonLogic "github.com/perfect-panel/server/internal/logic/common"
	iapmodel "github.com/perfect-panel/server/internal/model/iap/apple"
	"github.com/perfect-panel/server/internal/model/order"
	"github.com/perfect-panel/server/internal/model/subscribe"
	"github.com/perfect-panel/server/internal/model/user"
	"github.com/perfect-panel/server/internal/svc"
	"github.com/perfect-panel/server/internal/types"
	iapapple "github.com/perfect-panel/server/pkg/iap/apple"
	"github.com/perfect-panel/server/pkg/logger"
	"github.com/perfect-panel/server/pkg/tool"
	"gorm.io/gorm"
)

// AppleIAPNotifyLogic 用于处理 App Store Server Notifications V2 的苹果内购通知
// 负责：JWS 验签、事务记录写入/撤销更新、订阅生命周期同步（续期/撤销等）
type AppleIAPNotifyLogic struct {
	logger.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

// NewAppleIAPNotifyLogic 创建通知处理逻辑实例
// 参数：
// - ctx: 请求上下文
// - svcCtx: 服务上下文，包含 DB/Redis/配置 等
// 返回：
// - *AppleIAPNotifyLogic: 通知处理逻辑对象
func NewAppleIAPNotifyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AppleIAPNotifyLogic {
	return &AppleIAPNotifyLogic{
		Logger: logger.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

// Handle 处理苹果内购通知
// 流程：
// 1. 验签通知信封，解析得到交易 JWS 并再次验签；
// 2. 写入或更新事务记录（幂等按 OriginalTransactionId）；
// 3. 依据产品映射更新订阅到期时间或撤销状态；
// 4. 全流程关键节点输出详细中文日志，便于定位问题。
// 参数：
// - signedPayload: 通知信封的 JWS（包含 data.signedTransactionInfo）
// 返回：
// - error: 处理失败错误，成功返回 nil
func (l *AppleIAPNotifyLogic) Handle(signedPayload string) error {
	txPayload, ntype, err := iapapple.VerifyNotificationSignedPayload(signedPayload)
	if err != nil {
		// 验签失败，记录错误以便排查（通常为 JWS 格式/证书链问题）
		l.Errorw("iap notify verify failed", logger.Field("error", err.Error()))
		return err
	}
	// 验签通过，记录通知类型与关键交易标识
	l.Infow("iap notify verified", logger.Field("type", ntype), logger.Field("productId", txPayload.ProductId), logger.Field("originalTransactionId", txPayload.OriginalTransactionId))
	commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_notify_received",
		"[SubscriptionFlow] apple iap server notification received",
		logger.Field("notify_type", ntype),
		logger.Field("product_id", txPayload.ProductId),
		logger.Field("original_transaction_tail", commonLogic.SensitiveTail(txPayload.OriginalTransactionId)),
		logger.Field("transaction_id_tail", commonLogic.SensitiveTail(txPayload.TransactionId)),
	)
	return l.svcCtx.DB.Transaction(func(db *gorm.DB) error {
		var existing *iapmodel.Transaction
		existing, _ = iapmodel.NewModel(l.svcCtx.DB, l.svcCtx.Redis).FindByOriginalId(l.ctx, txPayload.OriginalTransactionId)
		if existing == nil || existing.Id == 0 {
			// 首次出现该事务，写入记录
			rec := &iapmodel.Transaction{
				UserId:                0,
				OriginalTransactionId: txPayload.OriginalTransactionId,
				TransactionId:         txPayload.TransactionId,
				ProductId:             txPayload.ProductId,
				PurchaseAt:            txPayload.PurchaseDate,
				RevocationAt:          txPayload.RevocationDate,
				JWSHash:               "",
			}
			if e := db.Model(&iapmodel.Transaction{}).Create(rec).Error; e != nil {
				// 事务写入失败（唯一约束/字段问题），输出详细日志
				l.Errorw("iap notify insert transaction error", logger.Field("error", e.Error()), logger.Field("productId", txPayload.ProductId), logger.Field("originalTransactionId", txPayload.OriginalTransactionId))
				return e
			}
			existing = rec
		} else {
			if txPayload.RevocationDate != nil {
				// 撤销场景：更新 revocation_at
				if e := db.Model(&iapmodel.Transaction{}).
					Where("original_transaction_id = ?", txPayload.OriginalTransactionId).
					Update("revocation_at", txPayload.RevocationDate).Error; e != nil {
					// 撤销更新失败，记录日志
					l.Errorw("iap notify update revocation error", logger.Field("error", e.Error()), logger.Field("originalTransactionId", txPayload.OriginalTransactionId))
					return e
				}
			}
		}

		// Fix Bug 7: resolve userId when the IAP transaction record has no associated user.
		// This can happen if the first notification for a subscription arrived before the
		// in-app purchase flow created the order (race) or the order was made in a previous
		// build that did not write user_id to the IAP transaction table.
		if existing != nil && existing.UserId == 0 {
			var origOrder order.Order
			if lookupErr := db.Model(&order.Order{}).
				Where("trade_no = ? AND method = ?", txPayload.OriginalTransactionId, "apple_iap").
				Order("id ASC").
				First(&origOrder).Error; lookupErr == nil && origOrder.UserId > 0 {
				existing.UserId = origOrder.UserId
				_ = db.Model(&iapmodel.Transaction{}).
					Where("id = ?", existing.Id).
					Update("user_id", origOrder.UserId).Error
				l.Infow("iap notify resolved zero userId from original purchase order",
					logger.Field("userId", origOrder.UserId),
					logger.Field("originalTransactionId", txPayload.OriginalTransactionId),
				)
			} else {
				l.Errorw("CRITICAL: iap notify UserId=0 and cannot resolve from order, notification dropped",
					logger.Field("originalTransactionId", txPayload.OriginalTransactionId))
				return fmt.Errorf("iap notify: UserId=0 and cannot resolve from order for original_transaction_id=%s", txPayload.OriginalTransactionId)
			}
		}

		var days int64
		{
			pid := strings.ToLower(txPayload.ProductId)
			parts := strings.Split(pid, ".")
			for i := len(parts) - 1; i >= 0; i-- {
				p := parts[i]
				var unit string
				if strings.HasPrefix(p, "day") {
					unit = "Day"
					p = p[len("day"):]
				} else if strings.HasPrefix(p, "month") {
					unit = "Month"
					p = p[len("month"):]
				} else if strings.HasPrefix(p, "year") {
					unit = "Year"
					p = p[len("year"):]
				}
				if unit != "" {
					digits := p
					for j := 0; j < len(digits); j++ {
						if digits[j] < '0' || digits[j] > '9' {
							digits = digits[:j]
							break
						}
					}
					if q, e := strconv.ParseInt(digits, 10, 64); e == nil && q > 0 {
						switch unit {
						case "Day":
							days = q
						case "Month":
							days = q * 30
						case "Year":
							days = q * 365
						}
						break
					}
				}
			}
		}
		if days == 0 {
			_, subs, e := l.svcCtx.SubscribeModel.FilterList(l.ctx, &subscribe.FilterParams{
				Page:            1,
				Size:            9999,
				Show:            true,
				Sell:            true,
				DefaultLanguage: true,
			})
			if e == nil && len(subs) > 0 {
				for _, item := range subs {
					var discounts []types.SubscribeDiscount
					if item.Discount != "" {
						_ = json.Unmarshal([]byte(item.Discount), &discounts)
					}
					for _, d := range discounts {
						if strings.Contains(strings.ToLower(txPayload.ProductId), strings.ToLower(item.UnitTime)) && d.Quantity > 0 {
							// fallback not strict
							if item.UnitTime == "Day" {
								days = int64(d.Quantity)
							} else if item.UnitTime == "Month" {
								days = int64(d.Quantity) * 30
							} else if item.UnitTime == "Year" {
								days = int64(d.Quantity) * 365
							}
							break
						}
					}
					if days > 0 {
						break
					}
				}
			}
		}
		if days == 0 {
			// Both string-parse and DB fallback failed to map the product to days.
			// Return an error so Apple retries the notification; silently ignoring
			// this would cause the subscriber's renewal to be lost.
			l.Errorw("CRITICAL: iap notify product mapping missing, returning error to trigger retry",
				logger.Field("productId", txPayload.ProductId))
			return fmt.Errorf("iap product id %s could not be mapped to subscription days", txPayload.ProductId)
		}
		token := "iap:" + txPayload.OriginalTransactionId
		sub, e := l.svcCtx.UserModel.FindOneSubscribeByToken(l.ctx, token)
		if (e != nil || sub == nil || sub.Id == 0) && existing != nil && existing.UserId > 0 && days > 0 {
			// 首购订单创建的订阅 token 不是 iap: 前缀，尝试按 userId+subscribeId 查找
			l.Infow("iap notify fallback: find subscribe by userId", logger.Field("userId", existing.UserId))
			userSubs, queryErr := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, existing.UserId, 0, 1, 2, 3)
			if queryErr == nil {
				exp := iapapple.CalcExpire(txPayload.PurchaseDate, days)
				for _, us := range userSubs {
					if us == nil {
						continue
					}
					candidate := &user.Subscribe{
						Id:          us.Id,
						UserId:      us.UserId,
						SubscribeId: us.SubscribeId,
						ExpireTime:  us.ExpireTime,
						Status:      us.Status,
						Token:       us.Token,
						FinishedAt:  us.FinishedAt,
					}
					if txPayload.RevocationDate != nil {
						candidate.Status = 3
						t := *txPayload.RevocationDate
						candidate.FinishedAt = &t
						candidate.ExpireTime = t
					} else {
						if exp.After(candidate.ExpireTime) {
							candidate.ExpireTime = exp
						}
						candidate.Status = 1
						candidate.FinishedAt = nil
					}
					if err := l.svcCtx.UserModel.UpdateSubscribe(l.ctx, candidate, db); err != nil {
						l.Errorw("iap notify fallback update subscribe error", logger.Field("error", err.Error()), logger.Field("userSubscribeId", candidate.Id))
						return err
					}
					l.Infow("iap notify fallback updated subscribe", logger.Field("userSubscribeId", candidate.Id), logger.Field("status", candidate.Status))
					commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "subscription_updated_from_notify",
						"[SubscriptionFlow] apple iap notify updated fallback subscription candidate",
						append(commonLogic.UserSubscribeTraceFields(candidate),
							logger.Field("notify_type", ntype),
							logger.Field("product_id", txPayload.ProductId),
						)...,
					)
					// Create audit order record for renewal notifications (Bug 7)
					if err := l.createIAPRenewalAuditOrder(db, ntype, txPayload.TransactionId, candidate.UserId, candidate.SubscribeId, candidate.Token); err != nil {
						l.Errorw("iap notify fallback create renewal order error", logger.Field("error", err.Error()))
						// Non-fatal: subscription already updated; order creation failure is logged only
					}
					break
				}
			}
			return nil
		}
		if e == nil && sub != nil && sub.Id != 0 {
			if txPayload.RevocationDate != nil {
				// 撤销：订阅置为过期并记录完成时间
				sub.Status = 3
				t := *txPayload.RevocationDate
				sub.FinishedAt = &t
				sub.ExpireTime = t
			} else if days > 0 {
				// 正常：根据映射天数续期
				exp := iapapple.CalcExpire(txPayload.PurchaseDate, days)
				sub.ExpireTime = exp
				sub.Status = 1
			}
			if e := l.svcCtx.UserModel.UpdateSubscribe(l.ctx, sub, db); e != nil {
				// 订阅更新失败，记录日志
				l.Errorw("iap notify update subscribe error", logger.Field("error", e.Error()), logger.Field("userSubscribeId", sub.Id))
				return e
			}
			// 更新成功，输出订阅状态
			l.Infow("iap notify updated subscribe", logger.Field("userSubscribeId", sub.Id), logger.Field("status", sub.Status))
			commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "subscription_updated_from_notify",
				"[SubscriptionFlow] apple iap notify updated subscription",
				append(commonLogic.UserSubscribeTraceFields(sub),
					logger.Field("notify_type", ntype),
					logger.Field("product_id", txPayload.ProductId),
				)...,
			)
			// Create audit order record for renewal notifications (Bug 7)
			if err := l.createIAPRenewalAuditOrder(db, ntype, txPayload.TransactionId, sub.UserId, sub.SubscribeId, sub.Token); err != nil {
				l.Errorw("iap notify create renewal order error", logger.Field("error", err.Error()))
				// Non-fatal: subscription already updated; order creation failure is logged only
			}
		}
		return nil
	})
}

// createIAPRenewalAuditOrder creates a finished renewal order record for DID_RENEW and SUBSCRIBED
// Apple SSNS notifications so that the order table has a complete financial audit trail.
// The operation is idempotent: if an order with the same trade_no already exists it is skipped.
func (l *AppleIAPNotifyLogic) createIAPRenewalAuditOrder(db *gorm.DB, ntype, transactionId string, userId, subscribeId int64, subscribeToken string) error {
	if ntype != "DID_RENEW" && ntype != "SUBSCRIBED" {
		return nil
	}
	// Idempotency check
	var count int64
	if err := db.Model(&order.Order{}).
		Where("trade_no = ? AND method = ?", transactionId, "apple_iap").
		Count(&count).Error; err != nil {
		return fmt.Errorf("check existing iap renewal order: %w", err)
	}
	if count > 0 {
		return nil
	}
	rec := &order.Order{
		UserId:         userId,
		OrderNo:        tool.GenerateTradeNo(),
		Type:           2, // OrderTypeRenewal
		Status:         5, // OrderStatusFinished
		Method:         "apple_iap",
		TradeNo:        transactionId,
		SubscribeId:    subscribeId,
		SubscribeToken: subscribeToken,
		Quantity:       1,
		IsNew:          false,
	}
	if err := db.Model(&order.Order{}).Create(rec).Error; err != nil {
		return fmt.Errorf("create iap renewal audit order: %w", err)
	}
	l.Infow("iap notify created renewal audit order",
		logger.Field("orderNo", rec.OrderNo),
		logger.Field("transactionId", transactionId),
		logger.Field("userId", userId),
	)
	return nil
}
