package mysql

import (
	"context"
	"database/sql"
	"errors"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
)

func (r *Repository) creditHostPeriodDiamonds(ctx context.Context, tx *sql.Tx, transactionID string, commandID string, metadata giftMetadata, nowMs int64) (int64, int64, error) {

	if metadata.HostPeriodDiamondAdded <= 0 {
		return 0, 0, nil
	}
	after, version, exists, err := r.lockHostPeriodDiamondAccount(ctx, tx, metadata.TargetUserID, metadata.HostPeriodCycleKey)
	if err != nil {
		return 0, 0, err
	}
	if !exists {

		after = metadata.HostPeriodDiamondAdded
		version = 1
		_, err = tx.ExecContext(ctx,
			`INSERT INTO host_period_diamond_accounts (
			   app_code, user_id, cycle_key, region_id, agency_owner_user_id, total_diamonds, gift_diamond_total,
			   version, first_gift_at_ms, last_gift_at_ms, created_at_ms, updated_at_ms
			 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
			appcode.FromContext(ctx),
			metadata.TargetUserID,
			metadata.HostPeriodCycleKey,
			metadata.TargetHostRegionID,
			metadata.TargetAgencyOwnerUserID,
			after,
			metadata.HostPeriodDiamondAdded,
			version,
			nowMs,
			nowMs,
			nowMs,
			nowMs,
		)
		if err != nil && isMySQLDuplicateError(err) {

			after, version, exists, err = r.lockHostPeriodDiamondAccount(ctx, tx, metadata.TargetUserID, metadata.HostPeriodCycleKey)
		}
		if err != nil {
			return 0, 0, err
		}
		if !exists {
			if err := r.insertHostPeriodDiamondEntry(ctx, tx, transactionID, commandID, metadata, after, nowMs); err != nil {
				return 0, 0, err
			}
			return after, version, nil
		}
	}

	after, err = checkedAdd(after, metadata.HostPeriodDiamondAdded)
	if err != nil {
		return 0, 0, err
	}
	version++

	result, err := tx.ExecContext(ctx,
		`UPDATE host_period_diamond_accounts
		 SET region_id = ?, agency_owner_user_id = ?, total_diamonds = ?, gift_diamond_total = gift_diamond_total + ?,
		     version = ?, last_gift_at_ms = ?, updated_at_ms = ?
		 WHERE app_code = ? AND user_id = ? AND cycle_key = ?`,
		metadata.TargetHostRegionID,
		metadata.TargetAgencyOwnerUserID,
		after,
		metadata.HostPeriodDiamondAdded,
		version,
		nowMs,
		nowMs,
		appcode.FromContext(ctx),
		metadata.TargetUserID,
		metadata.HostPeriodCycleKey,
	)
	if err != nil {
		return 0, 0, err
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return 0, 0, err
	}
	if rows != 1 {
		return 0, 0, xerr.New(xerr.LedgerConflict, "host period diamond account version conflict")
	}
	if err := r.insertHostPeriodDiamondEntry(ctx, tx, transactionID, commandID, metadata, after, nowMs); err != nil {
		return 0, 0, err
	}
	return after, version, nil
}

func (r *Repository) lockHostPeriodDiamondAccount(ctx context.Context, tx *sql.Tx, userID int64, cycleKey string) (int64, int64, bool, error) {

	row := tx.QueryRowContext(ctx,
		`SELECT total_diamonds, version
		 FROM host_period_diamond_accounts
		 WHERE app_code = ? AND user_id = ? AND cycle_key = ?
		 FOR UPDATE`,
		appcode.FromContext(ctx),
		userID,
		cycleKey,
	)
	var totalDiamonds int64
	var version int64
	if err := row.Scan(&totalDiamonds, &version); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return 0, 0, false, nil
		}
		return 0, 0, false, err
	}
	return totalDiamonds, version, true, nil
}

func (r *Repository) insertHostPeriodDiamondEntry(ctx context.Context, tx *sql.Tx, transactionID string, commandID string, metadata giftMetadata, diamondAfter int64, nowMs int64) error {

	_, err := tx.ExecContext(ctx,
		`INSERT INTO host_period_diamond_entries (
		   app_code, transaction_id, command_id, user_id, cycle_key, region_id, agency_owner_user_id,
		   diamond_delta, diamond_after, gift_charge_asset_type, gift_charge_amount,
		   gift_id, gift_count, sender_user_id, room_id, created_at_ms
		 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		appcode.FromContext(ctx),
		transactionID,
		commandID,
		metadata.TargetUserID,
		metadata.HostPeriodCycleKey,
		metadata.TargetHostRegionID,
		metadata.TargetAgencyOwnerUserID,
		metadata.HostPeriodDiamondAdded,
		diamondAfter,
		metadata.ChargeAssetType,
		metadata.ChargeAmount,
		metadata.GiftID,
		metadata.GiftCount,
		metadata.SenderUserID,
		metadata.RoomID,
		nowMs,
	)
	return err
}

func hostPeriodCycleKeyFromMS(ms int64) string {

	return time.UnixMilli(ms).UTC().Format("2006-01")
}
