package mysql

import (
	"context"
	"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) 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() }()

	resource, err := r.getResourceForUpdate(ctx, tx, command.ResourceID)
	if err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if resource.ResourceType != resourcedomain.TypeGift {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.InvalidArgument, "gift config must select gift resource")
	}
	command = applyResourcePricingToGiftCommand(command, resource)
	if resourcedomain.NormalizePriceType(resource.PriceType) == "" && command.CoinPrice <= 0 {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.InvalidArgument, "gift price is invalid")
	}
	if err := validateGiftConfigCommand(command); err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if command.Status == resourcedomain.StatusActive && resource.Status != resourcedomain.StatusActive {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.Conflict, "gift resource is disabled")
	}
	if err := r.ensureDefaultGiftTypeConfigs(ctx, tx); err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if ok, err := r.giftTypeConfigExistsTx(ctx, tx, command.GiftTypeCode); err != nil {
		return resourcedomain.GiftConfig{}, err
	} else if !ok {
		return resourcedomain.GiftConfig{}, xerr.New(xerr.InvalidArgument, "gift type is not configured")
	}
	nowMs := time.Now().UnixMilli()

	presentation := giftPresentationWithCPRelationType(command.PresentationJSON, command.CPRelationType)
	effectTypesJSON, err := json.Marshal(command.EffectTypes)
	if err != nil {
		return resourcedomain.GiftConfig{}, err
	}
	if !update {
		if _, err := tx.ExecContext(ctx, `
			INSERT INTO gift_configs (
			   app_code, gift_id, resource_id, status, name, sort_order, presentation_json,
			   gift_type_code, 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.EffectiveFromMS, command.EffectiveToMS, string(effectTypesJSON),
			command.OperatorUserID, command.OperatorUserID, nowMs, nowMs,
		); err != nil {
			return resourcedomain.GiftConfig{}, err
		}
	} else {
		result, err := tx.ExecContext(ctx, `
			UPDATE gift_configs
			SET resource_id = ?, status = ?, name = ?, sort_order = ?, presentation_json = ?,
			    gift_type_code = ?, 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.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
	}
	return r.getGiftConfig(ctx, command.GiftID)
}
