// Package agentrecharge 收口员工线下代充值申请和审批终态业务用例。
package agentrecharge

import (
	"context"
	"fmt"
	"strings"

	"github.com/bytedance/sonic"
	"gorm.io/gorm"

	approvalapp "github.com/break/junhong_cmp_fiber/internal/application/approval"
	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
)

// CreateOfflineCommand 描述员工创建线下代充值审批申请的稳定输入。
type CreateOfflineCommand struct {
	SubmitterAccountID uint
	SubmitterUserType  int
	ShopID             uint
	RechargeNo         string
	Amount             int64
	PaymentVoucherKeys []string
	Remark             string
}

// CreateOfflineResult 返回已原子保存的业务申请和初始审批状态。
type CreateOfflineResult struct {
	Record         *model.AgentRechargeRecord
	ShopName       string
	SubmitterName  string
	ApprovalStatus int
}

// OfflineCreationService 创建员工线下代充值申请及唯一通用审批实例。
type OfflineCreationService struct {
	db       *gorm.DB
	approval approvalapp.Port
}

// NewOfflineCreationService 创建员工线下代充值申请用例。
func NewOfflineCreationService(db *gorm.DB, approval approvalapp.Port) *OfflineCreationService {
	return &OfflineCreationService{db: db, approval: approval}
}

// Execute 在业务写入前校验审批渠道，并在同一事务保存充值申请、审批实例和提交 Outbox。
func (s *OfflineCreationService) Execute(ctx context.Context, command CreateOfflineCommand) (*CreateOfflineResult, error) {
	if s == nil || s.db == nil || s.approval == nil {
		return nil, errors.New(errors.CodeServiceUnavailable, "员工线下代充值审批能力未配置")
	}
	if err := validateCreateOfflineCommand(command); err != nil {
		return nil, err
	}
	account, shop, wallet, err := s.loadCreationFacts(ctx, command)
	if err != nil {
		return nil, err
	}
	preparation, err := s.approval.Prepare(ctx, approvalapp.PrepareRequest{
		BusinessType: constants.ApprovalBusinessTypeOfflineRecharge, SubmitterAccountID: command.SubmitterAccountID,
		CorrelationID: strings.TrimSpace(command.RechargeNo),
	})
	if err != nil {
		return nil, err
	}
	submitterSnapshot, requestSnapshot, err := offlineApprovalSnapshots(command, account.Username, shop.ShopName)
	if err != nil {
		return nil, err
	}
	paymentChannel := constants.RechargeMethodOffline
	record := &model.AgentRechargeRecord{
		UserID: command.SubmitterAccountID, AgentWalletID: wallet.ID, ShopID: command.ShopID,
		RechargeNo: strings.TrimSpace(command.RechargeNo), Amount: command.Amount,
		PaymentMethod: constants.RechargeMethodOffline, PaymentChannel: &paymentChannel,
		PaymentVoucherKey: model.StringJSONBArray(command.PaymentVoucherKeys), Remark: strings.TrimSpace(command.Remark),
		Status: constants.RechargeStatusPending, ShopIDTag: wallet.ShopIDTag, EnterpriseIDTag: wallet.EnterpriseIDTag,
	}
	var approvalStatus int
	err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.WithContext(ctx).Create(record).Error; err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "创建员工线下代充值申请失败")
		}
		reference, err := s.approval.CreateInTx(ctx, tx, approvalapp.CreateRequest{
			Preparation: preparation, BusinessType: constants.ApprovalBusinessTypeOfflineRecharge,
			BusinessID: record.ID, SubmitterAccountID: command.SubmitterAccountID,
			SubmitterSnapshot: submitterSnapshot, RequestSnapshot: requestSnapshot,
			CorrelationID: record.RechargeNo,
		})
		if err != nil {
			return err
		}
		result := tx.WithContext(ctx).Model(&model.AgentRechargeRecord{}).
			Where("id = ? AND approval_instance_id IS NULL", record.ID).
			Update("approval_instance_id", reference.InstanceID)
		if result.Error != nil {
			return errors.Wrap(errors.CodeDatabaseError, result.Error, "关联员工线下代充值审批实例失败")
		}
		if result.RowsAffected != 1 {
			return errors.New(errors.CodeConflict, "员工线下代充值审批实例关联已变化")
		}
		record.ApprovalInstanceID = &reference.InstanceID
		approvalStatus = reference.Status
		return nil
	})
	if err != nil {
		return nil, err
	}
	return &CreateOfflineResult{
		Record: record, ShopName: shop.ShopName, SubmitterName: account.Username, ApprovalStatus: approvalStatus,
	}, nil
}

func validateCreateOfflineCommand(command CreateOfflineCommand) error {
	if command.SubmitterUserType != constants.UserTypePlatform && command.SubmitterUserType != constants.UserTypeSuperAdmin {
		return errors.New(errors.CodeForbidden, "线下充值仅平台管理员可操作")
	}
	if command.SubmitterAccountID == 0 || command.ShopID == 0 || strings.TrimSpace(command.RechargeNo) == "" {
		return errors.New(errors.CodeInvalidParam)
	}
	if command.Amount < constants.AgentRechargeMinAmount || command.Amount > constants.AgentRechargeMaxAmount {
		return errors.New(errors.CodeInvalidParam, "充值金额超出允许范围")
	}
	if len(command.PaymentVoucherKeys) == 0 || len(command.PaymentVoucherKeys) > 5 {
		return errors.New(errors.CodeInvalidParam, "线下充值必须上传 1 至 5 个支付凭证")
	}
	for _, key := range command.PaymentVoucherKeys {
		if strings.TrimSpace(key) == "" {
			return errors.New(errors.CodeInvalidParam, "线下充值支付凭证不能为空")
		}
	}
	return nil
}

func (s *OfflineCreationService) loadCreationFacts(
	ctx context.Context,
	command CreateOfflineCommand,
) (*model.Account, *model.Shop, *model.AgentWallet, error) {
	var account model.Account
	if err := s.db.WithContext(ctx).Where("id = ? AND status = ?", command.SubmitterAccountID, constants.StatusEnabled).First(&account).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil, nil, errors.New(errors.CodeForbidden, "提交人账号不可用")
		}
		return nil, nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询线下代充值提交人失败")
	}
	var shop model.Shop
	if err := s.db.WithContext(ctx).Where("id = ?", command.ShopID).First(&shop).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil, nil, errors.New(errors.CodeNotFound, "目标店铺不存在")
		}
		return nil, nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询目标店铺失败")
	}
	var wallet model.AgentWallet
	if err := s.db.WithContext(ctx).
		Where("shop_id = ? AND wallet_type = ? AND status = ?", command.ShopID, constants.AgentWalletTypeMain, constants.AgentWalletStatusNormal).
		First(&wallet).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil, nil, errors.New(errors.CodeWalletNotFound, "目标店铺主钱包不存在或不可用")
		}
		return nil, nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询目标店铺主钱包失败")
	}
	return &account, &shop, &wallet, nil
}

func offlineApprovalSnapshots(command CreateOfflineCommand, submitterName, shopName string) ([]byte, []byte, error) {
	submitterSnapshot, err := sonic.Marshal(map[string]any{
		"account_id": command.SubmitterAccountID, "account_name": submitterName,
		"user_type": command.SubmitterUserType,
	})
	if err != nil {
		return nil, nil, errors.Wrap(errors.CodeInternalError, err, "编码线下代充值提交人快照失败")
	}
	requestSnapshot, err := sonic.Marshal(map[string]any{
		constants.ApprovalFieldRechargeNo:        strings.TrimSpace(command.RechargeNo),
		constants.ApprovalFieldShopID:            command.ShopID,
		constants.ApprovalFieldShopName:          shopName,
		constants.ApprovalFieldAmount:            fmt.Sprintf("%d.%02d", command.Amount/100, command.Amount%100),
		constants.ApprovalFieldAmountCent:        command.Amount,
		constants.ApprovalFieldPaymentVoucherKey: command.PaymentVoucherKeys,
		constants.ApprovalFieldRemark:            strings.TrimSpace(command.Remark),
		constants.ApprovalFieldSubmitterID:       command.SubmitterAccountID,
		constants.ApprovalFieldSubmitterName:     submitterName,
	})
	if err != nil {
		return nil, nil, errors.Wrap(errors.CodeInternalError, err, "编码线下代充值审批业务快照失败")
	}
	return submitterSnapshot, requestSnapshot, nil
}
