package recovery

import (
	"context"
	"errors"
	"strings"
	"time"

	"github.com/google/uuid"
	commonLogic "github.com/perfect-panel/server/internal/logic/common"
	ordermodel "github.com/perfect-panel/server/internal/model/order"
	recoverymodel "github.com/perfect-panel/server/internal/model/recovery"
	subscribemodel "github.com/perfect-panel/server/internal/model/subscribe"
	usermodel "github.com/perfect-panel/server/internal/model/user"
	recoveryconfig "github.com/perfect-panel/server/internal/recovery"
	"github.com/perfect-panel/server/internal/svc"
	"github.com/perfect-panel/server/internal/types"
	"github.com/perfect-panel/server/pkg/authmethod"
	"github.com/perfect-panel/server/pkg/constant"
	"github.com/perfect-panel/server/pkg/logger"
	"github.com/perfect-panel/server/pkg/tool"
	"github.com/perfect-panel/server/pkg/uuidx"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

const (
	recoverySuccessMessage      = "恢复完成，请使用该邮箱验证码登录查看订阅。"
	recoveryAlreadyDoneMessage  = "该订单已恢复，请使用恢复时绑定的邮箱登录查看订阅。"
	recoveryInvalidOrderMessage = "订单信息无效，请确认订单号是否正确。"
	recoveryInvalidCodeMessage  = "邮箱验证码错误或已过期，请重新获取验证码。"
	recoverySystemErrorMessage  = "恢复暂时失败，请稍后再试或联系客服处理。"
)

type RecoverOrderLogic struct {
	logger.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func NewRecoverOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RecoverOrderLogic {
	return &RecoverOrderLogic{
		Logger: logger.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *RecoverOrderLogic) RecoverOrder(req *types.RecoverOrderRequest) (*types.RecoverOrderResponse, error) {
	orderNo := strings.TrimSpace(req.OrderNo)
	email := strings.ToLower(strings.TrimSpace(req.Email))
	code := strings.TrimSpace(req.Code)

	if orderNo == "" || email == "" || code == "" {
		return recoveryFailed(recoveryInvalidOrderMessage), nil
	}

	if err := l.verifyEmailCode(email, code); err != nil {
		return recoveryFailed(recoveryInvalidCodeMessage), nil
	}

	item, ok, err := recoveryconfig.FindOrder(orderNo)
	if err != nil {
		l.Errorw("[RecoverOrder] load recovery config failed", logger.Field("error", err.Error()))
		return recoveryFailed(recoverySystemErrorMessage), nil
	}
	if !ok {
		return recoveryFailed(recoveryInvalidOrderMessage), nil
	}

	if item.OrderStatus != "" && item.OrderStatus != "success" {
		return recoveryFailed(recoveryInvalidOrderMessage), nil
	}
	if item.Quantity <= 0 || item.OrderMoneyCents <= 0 {
		return recoveryFailed(recoveryInvalidOrderMessage), nil
	}
	claimOrderNo := item.OutOrderNo
	if claimOrderNo == "" {
		claimOrderNo = item.OrderNo
	}

	sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, item.SubscribeId)
	if err != nil {
		l.Errorw("[RecoverOrder] subscribe not found", logger.Field("subscribe_id", item.SubscribeId), logger.Field("error", err.Error()))
		return recoveryFailed(recoverySystemErrorMessage), nil
	}

	var (
		claim            recoverymodel.OrderRecoveryClaim
		order            ordermodel.Order
		userSub          usermodel.Subscribe
		alreadyRecovered bool
	)
	err = l.svcCtx.DB.WithContext(l.ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Model(&recoverymodel.OrderRecoveryClaim{}).
			Where("order_no = ?", claimOrderNo).
			First(&claim).Error; err == nil {
			alreadyRecovered = true
			return nil
		} else if !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}

		u, err := l.findOrCreateUser(tx, email)
		if err != nil {
			return err
		}

		claim = recoverymodel.OrderRecoveryClaim{
			OrderNo:     claimOrderNo,
			Email:       email,
			UserId:      u.Id,
			SubscribeId: item.SubscribeId,
			ClaimedAt:   time.Now(),
		}
		if err := tx.Model(&recoverymodel.OrderRecoveryClaim{}).Create(&claim).Error; err != nil {
			return err
		}

		order, err = l.createRecoveredOrder(tx, u.Id, item, sub)
		if err != nil {
			return err
		}

		userSub, err = l.createOrExtendSubscribe(tx, u.Id, order.Id, item, sub)
		if err != nil {
			return err
		}

		claim.UserSubscribeId = userSub.Id
		return tx.Model(&recoverymodel.OrderRecoveryClaim{}).
			Where("id = ?", claim.Id).
			Updates(map[string]any{
				"order_id":          order.Id,
				"user_subscribe_id": userSub.Id,
			}).Error
	})
	if err != nil {
		l.Errorw("[RecoverOrder] recover failed", logger.Field("order_no", orderNo), logger.Field("error", err.Error()))
		return recoveryFailed(recoverySystemErrorMessage), nil
	}

	if alreadyRecovered {
		return &types.RecoverOrderResponse{
			Success: true,
			Message: recoveryAlreadyDoneMessage,
		}, nil
	}

	l.clearCaches(&userSub, item.SubscribeId, email)
	l.Infof("[RecoverOrder] recovered order subscription order_no=%s email=%s user_id=%d subscribe_id=%d user_subscribe_id=%d",
		orderNo, email, claim.UserId, claim.SubscribeId, claim.UserSubscribeId)

	return &types.RecoverOrderResponse{
		Success: true,
		Message: recoverySuccessMessage,
	}, nil
}

func recoveryFailed(message string) *types.RecoverOrderResponse {
	return &types.RecoverOrderResponse{
		Success: false,
		Message: message,
	}
}

func (l *RecoverOrderLogic) createRecoveredOrder(tx *gorm.DB, userId int64, item recoveryconfig.Order, sub *subscribemodel.Subscribe) (ordermodel.Order, error) {
	var existing ordermodel.Order
	orderNo := item.OutOrderNo
	if orderNo == "" {
		orderNo = item.OrderNo
	}
	if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
		Model(&ordermodel.Order{}).
		Where("order_no = ?", orderNo).
		First(&existing).Error; err == nil {
		if existing.UserId != userId {
			return ordermodel.Order{}, errors.New("recovered order number already belongs to another user")
		}
		return existing, nil
	} else if !errors.Is(err, gorm.ErrRecordNotFound) {
		return ordermodel.Order{}, err
	}

	paidAt := time.Now()
	if item.PaidAt > 0 {
		paidAt = time.Unix(item.PaidAt, 0)
	}

	quantity := item.Quantity
	if quantity <= 0 {
		quantity = 1
	}
	price := sub.UnitPrice * quantity
	amount := item.OrderMoneyCents
	if amount <= 0 {
		amount = price
	}

	o := ordermodel.Order{
		UserId:             userId,
		SubscriptionUserId: userId,
		OrderNo:            orderNo,
		Type:               1,
		Quantity:           quantity,
		Price:              price,
		Amount:             amount,
		Discount:           price - amount,
		PaymentId:          2,
		Method:             "EPay",
		TradeNo:            strings.TrimSpace(item.PaymentOrderNo),
		Status:             5,
		SubscribeId:        item.SubscribeId,
		IsNew:              true,
		CreatedAt:          paidAt,
		UpdatedAt:          paidAt,
	}
	if o.Discount < 0 {
		o.Discount = 0
	}
	if err := tx.Model(&ordermodel.Order{}).Create(&o).Error; err != nil {
		return ordermodel.Order{}, err
	}
	return o, nil
}

func (l *RecoverOrderLogic) createOrExtendSubscribe(tx *gorm.DB, userId int64, orderId int64, item recoveryconfig.Order, sub *subscribemodel.Subscribe) (usermodel.Subscribe, error) {
	now := time.Now()
	var existing usermodel.Subscribe
	err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
		Model(&usermodel.Subscribe{}).
		Where("user_id = ? AND subscribe_id = ?", userId, item.SubscribeId).
		Order("expire_time DESC").
		Order("id DESC").
		First(&existing).Error
	if err == nil {
		base := now
		if existing.ExpireTime.After(now) {
			base = existing.ExpireTime
		}
		existing.OrderId = orderId
		existing.ExpireTime = base.Add(time.Duration(item.Quantity) * 24 * time.Hour)
		existing.Status = 1
		existing.FinishedAt = nil
		if strings.TrimSpace(item.Note) != "" {
			existing.Note = strings.TrimSpace(item.Note)
		}
		if err := tx.Model(&usermodel.Subscribe{}).Where("id = ?", existing.Id).Save(&existing).Error; err != nil {
			return usermodel.Subscribe{}, err
		}
		return existing, nil
	} else if !errors.Is(err, gorm.ErrRecordNotFound) {
		return usermodel.Subscribe{}, err
	}

	orderNo := item.OutOrderNo
	if orderNo == "" {
		orderNo = item.OrderNo
	}
	userSub := usermodel.Subscribe{
		UserId:      userId,
		OrderId:     orderId,
		SubscribeId: item.SubscribeId,
		NodeGroupId: sub.NodeGroupId,
		StartTime:   now,
		ExpireTime:  now.Add(time.Duration(item.Quantity) * 24 * time.Hour),
		Traffic:     sub.Traffic,
		Download:    0,
		Upload:      0,
		Token:       uuidx.SubscribeToken(orderNo),
		UUID:        uuid.New().String(),
		Status:      1,
		Note:        strings.TrimSpace(item.Note),
	}
	if err := tx.Model(&usermodel.Subscribe{}).Create(&userSub).Error; err != nil {
		return usermodel.Subscribe{}, err
	}
	return userSub, nil
}

func (l *RecoverOrderLogic) verifyEmailCode(email string, code string) error {
	checker := commonLogic.NewCheckVerificationCodeLogic(l.ctx, l.svcCtx)
	resp, err := checker.CheckVerificationCodeWithBehavior(&types.CheckVerificationCodeRequest{
		Method:  authmethod.Email,
		Account: email,
		Code:    code,
		Type:    uint8(constant.Security),
	}, commonLogic.VerifyCodeCheckBehavior{
		Source:             "order_recovery",
		Consume:            true,
		AllowSceneFallback: true,
	})
	if err != nil {
		return err
	}
	if resp == nil || !resp.Status {
		return errors.New("invalid verification code")
	}
	return nil
}

func (l *RecoverOrderLogic) findOrCreateUser(tx *gorm.DB, email string) (*usermodel.User, error) {
	var auth usermodel.AuthMethods
	if err := tx.Model(&usermodel.AuthMethods{}).
		Where("auth_type = ? AND auth_identifier = ?", authmethod.Email, email).
		First(&auth).Error; err == nil {
		var u usermodel.User
		if err := tx.Model(&usermodel.User{}).Where("id = ?", auth.UserId).First(&u).Error; err != nil {
			return nil, err
		}
		u.AuthMethods = []usermodel.AuthMethods{auth}
		return &u, nil
	} else if !errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, err
	}

	u := &usermodel.User{
		Password: tool.EncodePassWord(uuid.NewString()),
		Algo:     "default",
		AuthMethods: []usermodel.AuthMethods{
			{
				AuthType:       authmethod.Email,
				AuthIdentifier: email,
				Verified:       true,
			},
		},
	}
	if err := tx.Create(u).Error; err != nil {
		return nil, err
	}
	referCode := uuidx.UserInviteCode(u.Id)
	if err := tx.Model(&usermodel.User{}).Where("id = ?", u.Id).Update("refer_code", referCode).Error; err != nil {
		return nil, err
	}
	u.ReferCode = referCode
	return u, nil
}

func (l *RecoverOrderLogic) clearCaches(userSub *usermodel.Subscribe, subscribeId int64, email string) {
	if userSub != nil && userSub.Id > 0 {
		if err := l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, userSub); err != nil {
			l.Errorw("[RecoverOrder] clear user subscribe cache failed", logger.Field("error", err.Error()))
		}
		if userSub.UserId > 0 {
			if err := l.svcCtx.UserModel.ClearUserCache(l.ctx, &usermodel.User{
				Id: userSub.UserId,
				AuthMethods: []usermodel.AuthMethods{
					{
						UserId:         userSub.UserId,
						AuthType:       authmethod.Email,
						AuthIdentifier: email,
					},
				},
			}); err != nil {
				l.Errorw("[RecoverOrder] clear user cache failed", logger.Field("error", err.Error()))
			}
		}
	}
	if subscribeId > 0 {
		if err := l.svcCtx.SubscribeModel.ClearCache(l.ctx, subscribeId); err != nil {
			l.Errorw("[RecoverOrder] clear subscribe cache failed", logger.Field("error", err.Error()))
		}
	}
	if l.svcCtx.NodeModel != nil {
		if err := l.svcCtx.NodeModel.ClearServerAllCache(l.ctx); err != nil {
			l.Errorw("[RecoverOrder] clear server cache failed", logger.Field("error", err.Error()))
		}
	}
}
