package user

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

	"hyapp/pkg/appcode"
	userdomain "hyapp/services/user-service/internal/domain/user"
)

// GetAvatarUpload 按完整租户主键读取上传凭证；PRIMARY KEY 保证资料更新和上传重试不会扫描媒体表。
func (r *Repository) GetAvatarUpload(ctx context.Context, userID int64, uploadID string) (userdomain.AvatarUploadRegistration, bool, error) {
	row := r.db.QueryRowContext(ctx, `
		SELECT app_code, user_id, avatar_upload_id, client_command_id, media_payload_sha256,
		       expected_object_key, object_url, status, content_type, media_format, size_bytes,
		       width, height, animated, frame_count, duration_ms, sha256, created_at_ms, updated_at_ms
		FROM user_avatar_upload_registrations
		WHERE app_code = ? AND user_id = ? AND avatar_upload_id = ?
		LIMIT 1`, appcode.FromContext(ctx), userID, strings.TrimSpace(uploadID))
	registration, err := scanAvatarUpload(row)
	if errors.Is(err, sql.ErrNoRows) {
		return userdomain.AvatarUploadRegistration{}, false, nil
	}
	return registration, err == nil, err
}

// RegisterAvatarUpload 原子占用客户端 command_id 派生的凭证；并发重试只允许完全相同的媒体载荷。
func (r *Repository) RegisterAvatarUpload(ctx context.Context, registration userdomain.AvatarUploadRegistration) (userdomain.AvatarUploadRegistration, error) {
	registration.AppCode = appcode.FromContext(ctx)
	registration.UploadID = strings.TrimSpace(registration.UploadID)
	registration.ClientCommandID = strings.TrimSpace(registration.ClientCommandID)
	registration.MediaPayloadSHA256 = strings.ToLower(strings.TrimSpace(registration.MediaPayloadSHA256))
	registration.ExpectedObjectKey = strings.TrimLeft(strings.TrimSpace(registration.ExpectedObjectKey), "/")
	registration.ObjectURL, registration.Status = "", "authorized"
	if registration.CreatedAtMS <= 0 {
		registration.CreatedAtMS = time.Now().UTC().UnixMilli()
	}
	if registration.UpdatedAtMS <= 0 {
		registration.UpdatedAtMS = registration.CreatedAtMS
	}
	_, err := r.db.ExecContext(ctx, `
		INSERT INTO user_avatar_upload_registrations (
		  app_code, user_id, avatar_upload_id, client_command_id, media_payload_sha256,
		  expected_object_key, object_url, status, content_type, media_format, size_bytes,
		  width, height, animated, frame_count, duration_ms, sha256, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, '', 'authorized', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
		ON DUPLICATE KEY UPDATE avatar_upload_id = user_avatar_upload_registrations.avatar_upload_id`,
		registration.AppCode, registration.UserID, registration.UploadID, registration.ClientCommandID,
		registration.MediaPayloadSHA256, registration.ExpectedObjectKey, registration.Media.ContentType,
		registration.Media.Format, registration.Media.SizeBytes, registration.Media.Width, registration.Media.Height,
		registration.Media.Animated, registration.Media.FrameCount, registration.Media.DurationMS, registration.Media.SHA256,
		registration.CreatedAtMS, registration.UpdatedAtMS)
	if err != nil {
		return userdomain.AvatarUploadRegistration{}, err
	}
	saved, exists, err := r.GetAvatarUpload(ctx, registration.UserID, registration.UploadID)
	if err != nil {
		return userdomain.AvatarUploadRegistration{}, err
	}
	if !exists || !sameAvatarUploadRecord(saved, registration) {
		return userdomain.AvatarUploadRegistration{}, userdomain.ErrAvatarUploadCommandConflict
	}
	return saved, nil
}

// CompleteAvatarUpload 只允许 authorized -> active；并发完成以首次受信 COS URL 为准。
func (r *Repository) CompleteAvatarUpload(ctx context.Context, registration userdomain.AvatarUploadRegistration) (userdomain.AvatarUploadRegistration, error) {
	registration.AppCode = appcode.FromContext(ctx)
	registration.ObjectURL = strings.TrimSpace(registration.ObjectURL)
	registration.UpdatedAtMS = time.Now().UTC().UnixMilli()
	_, err := r.db.ExecContext(ctx, `
		UPDATE user_avatar_upload_registrations
		SET object_url = ?, status = 'active', updated_at_ms = ?
		WHERE app_code = ? AND user_id = ? AND avatar_upload_id = ? AND status = 'authorized'`,
		registration.ObjectURL, registration.UpdatedAtMS, registration.AppCode, registration.UserID, registration.UploadID)
	if err != nil {
		return userdomain.AvatarUploadRegistration{}, err
	}
	saved, exists, err := r.GetAvatarUpload(ctx, registration.UserID, registration.UploadID)
	if err != nil {
		return userdomain.AvatarUploadRegistration{}, err
	}
	if !exists || !sameAvatarUploadRecord(saved, registration) || saved.Status != "active" || saved.ObjectURL != registration.ObjectURL {
		return userdomain.AvatarUploadRegistration{}, userdomain.ErrAvatarUploadCommandConflict
	}
	return saved, nil
}

func scanAvatarUpload(scanner interface{ Scan(dest ...any) error }) (userdomain.AvatarUploadRegistration, error) {
	var registration userdomain.AvatarUploadRegistration
	err := scanner.Scan(
		&registration.AppCode, &registration.UserID, &registration.UploadID, &registration.ClientCommandID,
		&registration.MediaPayloadSHA256, &registration.ExpectedObjectKey, &registration.ObjectURL, &registration.Status,
		&registration.Media.ContentType, &registration.Media.Format, &registration.Media.SizeBytes,
		&registration.Media.Width, &registration.Media.Height, &registration.Media.Animated,
		&registration.Media.FrameCount, &registration.Media.DurationMS, &registration.Media.SHA256,
		&registration.CreatedAtMS, &registration.UpdatedAtMS,
	)
	if err != nil {
		return userdomain.AvatarUploadRegistration{}, err
	}
	registration.Media.URL, registration.Media.ObjectKey = registration.ObjectURL, registration.ExpectedObjectKey
	registration.Media.Status = registration.Status
	return registration, nil
}

func sameAvatarUploadRecord(left userdomain.AvatarUploadRegistration, right userdomain.AvatarUploadRegistration) bool {
	return appcode.Normalize(left.AppCode) == appcode.Normalize(right.AppCode) && left.UserID == right.UserID &&
		strings.TrimSpace(left.UploadID) == strings.TrimSpace(right.UploadID) &&
		strings.TrimSpace(left.ClientCommandID) == strings.TrimSpace(right.ClientCommandID) &&
		strings.EqualFold(strings.TrimSpace(left.MediaPayloadSHA256), strings.TrimSpace(right.MediaPayloadSHA256)) &&
		strings.TrimLeft(strings.TrimSpace(left.ExpectedObjectKey), "/") == strings.TrimLeft(strings.TrimSpace(right.ExpectedObjectKey), "/") &&
		strings.EqualFold(left.Media.ContentType, right.Media.ContentType) && strings.EqualFold(left.Media.Format, right.Media.Format) &&
		left.Media.SizeBytes == right.Media.SizeBytes && left.Media.Width == right.Media.Width && left.Media.Height == right.Media.Height &&
		left.Media.Animated == right.Media.Animated && left.Media.FrameCount == right.Media.FrameCount &&
		left.Media.DurationMS == right.Media.DurationMS && strings.EqualFold(left.Media.SHA256, right.Media.SHA256)
}
