package mysql

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	resourcedomain "hyapp/services/wallet-service/internal/domain/resource"
	"strings"
	"time"
)

func (r *Repository) CreateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error) {
	return r.upsertGiftConfig(ctx, command, false)
}

func (r *Repository) BatchCreateGiftConfigs(ctx context.Context, commands []resourcedomain.GiftConfigCommand) ([]resourcedomain.GiftConfig, error) {
	if r == nil || r.db == nil {
		return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	if len(commands) == 0 {
		return nil, xerr.New(xerr.InvalidArgument, "gift configs are required")
	}
	ctx = contextWithCommandApp(ctx, commands[0].AppCode)
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer func() { _ = tx.Rollback() }()

	nowMs := time.Now().UnixMilli()
	gifts := make([]resourcedomain.GiftConfig, 0, len(commands))
	events := make([]walletOutboxEvent, 0, len(commands))
	for _, command := range commands {
		command.AppCode = appcode.FromContext(ctx)
		gift, err := r.createGiftConfigTx(ctx, tx, command, nowMs)
		if err != nil {
			return nil, err
		}
		gifts = append(gifts, gift)
		events = append(events, resourceOutboxEvent("GiftConfigChanged", fmt.Sprintf("gift:%s:config:%d", command.GiftID, nowMs), 0, command.ResourceID, command, nowMs))
	}
	if err := r.insertWalletOutbox(ctx, tx, events); err != nil {
		return nil, err
	}
	if err := tx.Commit(); err != nil {
		return nil, err
	}
	return gifts, nil
}

func (r *Repository) UpdateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error) {
	return r.upsertGiftConfig(ctx, command, true)
}

func (r *Repository) SetGiftConfigStatus(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error) {
	if r == nil || r.db == nil {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	if strings.TrimSpace(command.StringID) == "" {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.InvalidArgument, "gift_id is required")
	}
	ctx = contextWithCommandApp(ctx, command.AppCode)
	status := resourcedomain.NormalizeStatus(command.Status)
	if !resourcedomain.ValidStatus(status) {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.InvalidArgument, "status is invalid")
	}
	nowMs := time.Now().UnixMilli()
	result, err := r.db.ExecContext(ctx,
		`UPDATE gift_configs SET status = ?, updated_by_user_id = ?, updated_at_ms = ? WHERE app_code = ? AND gift_id = ?`,
		status, command.OperatorUserID, nowMs, appcode.FromContext(ctx), strings.TrimSpace(command.StringID),
	)
	if err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if affected, err := result.RowsAffected(); err != nil {
		return resourcedomain.GiftConfig{}, err
	} else if affected == 0 {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.NotFound, "gift config not found")
	}
	gift, err := r.getGiftConfig(ctx, strings.TrimSpace(command.StringID))
	if err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	_ = r.insertResourceOutboxNoTx(ctx, "GiftConfigChanged", fmt.Sprintf("gift:%s:status:%s:%d", gift.GiftID, status, nowMs), 0, gift.ResourceID, gift)
	return gift, nil
}

func (r *Repository) DeleteGiftConfig(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error) {
	if r == nil || r.db == nil {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	if strings.TrimSpace(command.StringID) == "" {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.InvalidArgument, "gift_id is required")
	}
	ctx = contextWithCommandApp(ctx, command.AppCode)
	giftID := strings.TrimSpace(command.StringID)
	nowMs := time.Now().UnixMilli()
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	defer func() { _ = tx.Rollback() }()

	gift, err := r.getGiftConfigForUpdateTx(ctx, tx, giftID)
	if err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if _, err := tx.ExecContext(ctx, `DELETE FROM gift_config_regions WHERE app_code = ? AND gift_id = ?`, appcode.FromContext(ctx), giftID); err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if _, err := tx.ExecContext(ctx, `DELETE FROM wallet_gift_prices WHERE app_code = ? AND gift_id = ?`, appcode.FromContext(ctx), giftID); err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	result, err := tx.ExecContext(ctx, `DELETE FROM gift_configs WHERE app_code = ? AND gift_id = ?`, appcode.FromContext(ctx), giftID)
	if err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if affected, err := result.RowsAffected(); err != nil {
		return resourcedomain.GiftConfig{}, err
	} else if affected == 0 {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.NotFound, "gift config not found")
	}

	deletedGift := gift
	deletedGift.Status = "deleted"

	if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
		resourceOutboxEvent("GiftConfigChanged", fmt.Sprintf("gift:%s:delete:%d", giftID, nowMs), 0, gift.ResourceID, deletedGift, nowMs),
	}); err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if err := tx.Commit(); err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	return gift, nil
}

func (r *Repository) upsertGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand, update bool) (resourcedomain.GiftConfig, error) {
	if r == nil || r.db == nil {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, command.AppCode)
	command.AppCode = appcode.FromContext(ctx)
	command = normalizeGiftConfigCommand(command)
	if command.ResourceID <= 0 {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.InvalidArgument, "gift config command is incomplete")
	}
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	defer func() { _ = tx.Rollback() }()

	nowMs := time.Now().UnixMilli()
	var gift resourcedomain.GiftConfig
	if !update {
		gift, err = r.createGiftConfigTx(ctx, tx, command, nowMs)
		if err != nil {
			return resourcedomain.GiftConfig{}, err
		}
	} else {
		if err := r.prepareGiftConfigCommandTx(ctx, tx, &command); err != nil {
			return resourcedomain.GiftConfig{}, err
		}
		presentation := giftPresentationWithCPRelationType(command.PresentationJSON, command.CPRelationType)
		effectTypesJSON, err := json.Marshal(command.EffectTypes)
		if err != nil {
			return resourcedomain.GiftConfig{}, err
		}
		result, err := tx.ExecContext(ctx, `
			UPDATE gift_configs
			SET resource_id = ?, status = ?, name = ?, sort_order = ?, presentation_json = ?,
			    gift_type_code = ?, vip_level = ?, effective_from_ms = ?, effective_to_ms = ?, effect_types_json = ?,
			    updated_by_user_id = ?, updated_at_ms = ?
			WHERE app_code = ? AND gift_id = ?`,
			command.ResourceID, command.Status, command.Name, command.SortOrder, presentation,
			command.GiftTypeCode, command.VIPLevel, command.EffectiveFromMS, command.EffectiveToMS, string(effectTypesJSON),
			command.OperatorUserID, nowMs, command.AppCode, command.GiftID,
		)
		if err != nil {
			return resourcedomain.GiftConfig{}, err
		}
		if affected, err := result.RowsAffected(); err != nil {
			return resourcedomain.GiftConfig{}, err
		} else if affected == 0 {
			return resourcedomain.GiftConfig{}, xerr.New(xerr.NotFound, "gift config not found")
		}
		if err := r.upsertGiftPriceTx(ctx, tx, command, nowMs); err != nil {
			return resourcedomain.GiftConfig{}, err
		}
		if err := r.replaceGiftConfigRegionsTx(ctx, tx, command.GiftID, command.RegionIDs, nowMs); err != nil {
			return resourcedomain.GiftConfig{}, err
		}
	}
	if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
		resourceOutboxEvent("GiftConfigChanged", fmt.Sprintf("gift:%s:config:%d", command.GiftID, nowMs), 0, command.ResourceID, command, nowMs),
	}); err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if err := tx.Commit(); err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if update {
		return r.getGiftConfig(ctx, command.GiftID)
	}
	return gift, nil
}

func (r *Repository) createGiftConfigTx(ctx context.Context, tx *sql.Tx, command resourcedomain.GiftConfigCommand, nowMs int64) (resourcedomain.GiftConfig, error) {
	if err := r.prepareGiftConfigCommandTx(ctx, tx, &command); err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	presentation := giftPresentationWithCPRelationType(command.PresentationJSON, command.CPRelationType)
	effectTypesJSON, err := json.Marshal(command.EffectTypes)
	if err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if _, err := tx.ExecContext(ctx, `
		INSERT INTO gift_configs (
		   app_code, gift_id, resource_id, status, name, sort_order, presentation_json,
		   gift_type_code, vip_level, effective_from_ms, effective_to_ms, effect_types_json,
		   created_by_user_id, updated_by_user_id, created_at_ms, updated_at_ms
		 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		command.AppCode, command.GiftID, command.ResourceID, command.Status, command.Name, command.SortOrder, presentation,
		command.GiftTypeCode, command.VIPLevel, command.EffectiveFromMS, command.EffectiveToMS, string(effectTypesJSON),
		command.OperatorUserID, command.OperatorUserID, nowMs, nowMs,
	); err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if err := r.upsertGiftPriceTx(ctx, tx, command, nowMs); err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if err := r.replaceGiftConfigRegionsTx(ctx, tx, command.GiftID, command.RegionIDs, nowMs); err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	return r.getGiftConfigForUpdateTx(ctx, tx, command.GiftID)
}

func (r *Repository) prepareGiftConfigCommandTx(ctx context.Context, tx *sql.Tx, command *resourcedomain.GiftConfigCommand) error {
	resource, err := r.getResourceForUpdate(ctx, tx, command.ResourceID)
	if err != nil {
		return err
	}
	if resource.ResourceType != resourcedomain.TypeGift {
		return xerr.New(xerr.InvalidArgument, "gift config must select gift resource")
	}
	*command = applyResourcePricingToGiftCommand(*command, resource)
	if resourcedomain.NormalizePriceType(resource.PriceType) == "" && command.CoinPrice <= 0 {
		return xerr.New(xerr.InvalidArgument, "gift price is invalid")
	}
	if err := validateGiftConfigCommand(*command); err != nil {
		return err
	}
	if command.Status == resourcedomain.StatusActive && resource.Status != resourcedomain.StatusActive {
		return xerr.New(xerr.Conflict, "gift resource is disabled")
	}
	if err := r.ensureDefaultGiftTypeConfigs(ctx, tx); err != nil {
		return err
	}
	if ok, err := r.giftTypeConfigExistsTx(ctx, tx, command.GiftTypeCode); err != nil {
		return err
	} else if !ok {
		return xerr.New(xerr.InvalidArgument, "gift type is not configured")
	}
	if resourcedomain.IsVIPGiftType(command.GiftTypeCode) {
		// vip_levels 主键以 (app_code, level) 点查，保存时拒绝不存在的等级，避免后台
		// 写入客户端永远无法匹配的 VIP 礼物配置；不要求等级启用，以兼容预配置流程。
		var exists int
		if err := tx.QueryRowContext(ctx, `
			SELECT 1 FROM vip_levels WHERE app_code = ? AND level = ? LIMIT 1`,
			command.AppCode, command.VIPLevel,
		).Scan(&exists); err != nil {
			if err == sql.ErrNoRows {
				return xerr.New(xerr.InvalidArgument, "vip gift level is not configured")
			}
			return err
		}
	}
	return nil
}
