package mysql

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

	"hyapp/pkg/appcode"
	roomservice "hyapp/services/room-service/internal/room/service"
)

// GetRoomMediaUploadRegistration 按上传 command_id 的主键读取首次授权事实。
// 该查询只命中 PRIMARY KEY，不扫描媒体表，可安全放在每次专用上传的权限检查之前。
func (r *Repository) GetRoomMediaUploadRegistration(ctx context.Context, roomID string, commandID string) (roomservice.RoomMediaUploadRegistration, bool, error) {
	row := r.db.QueryRowContext(ctx, `
		SELECT app_code, room_id, command_id, actor_user_id, purpose, media_payload_sha256,
		       expected_object_key, object_url, status, content_type, media_format, size_bytes,
		       width, height, animated, frame_count, duration_ms, sha256,
		       authorized_room_version, created_at_ms, updated_at_ms
		FROM room_media_upload_registrations
		WHERE app_code = ? AND room_id = ? AND command_id = ?
		LIMIT 1`, appcode.FromContext(ctx), strings.TrimSpace(roomID), strings.TrimSpace(commandID))
	registration, err := scanRoomMediaUploadRegistration(row)
	if errors.Is(err, sql.ErrNoRows) {
		return roomservice.RoomMediaUploadRegistration{}, false, nil
	}
	return registration, err == nil, err
}

// GetRoomMediaUploadByObjectKey 用唯一对象键读取任意生命周期的上传记录。
// 封面消费会修改主键 room_id；上传重试用该索引识别 consumed 事实，避免把唯一键冲突泄漏成数据库错误。
func (r *Repository) GetRoomMediaUploadByObjectKey(ctx context.Context, objectKey string) (roomservice.RoomMediaUploadRegistration, bool, error) {
	row := r.db.QueryRowContext(ctx, `
		SELECT app_code, room_id, command_id, actor_user_id, purpose, media_payload_sha256,
		       expected_object_key, object_url, status, content_type, media_format, size_bytes,
		       width, height, animated, frame_count, duration_ms, sha256,
		       authorized_room_version, created_at_ms, updated_at_ms
		FROM room_media_upload_registrations
		WHERE app_code = ? AND expected_object_key = ?
		LIMIT 1`, appcode.FromContext(ctx), strings.TrimLeft(strings.TrimSpace(objectKey), "/"))
	registration, err := scanRoomMediaUploadRegistration(row)
	if errors.Is(err, sql.ErrNoRows) {
		return roomservice.RoomMediaUploadRegistration{}, false, nil
	}
	return registration, err == nil, err
}

// GetActiveRoomMediaUploadByObjectKey 是 Save/Send 接受媒体事实前的权威反查。
// expected_object_key 有唯一索引，查询不会按 URL 或 JSON 字段扫描。
func (r *Repository) GetActiveRoomMediaUploadByObjectKey(ctx context.Context, objectKey string) (roomservice.RoomMediaUploadRegistration, bool, error) {
	row := r.db.QueryRowContext(ctx, `
		SELECT app_code, room_id, command_id, actor_user_id, purpose, media_payload_sha256,
		       expected_object_key, object_url, status, content_type, media_format, size_bytes,
		       width, height, animated, frame_count, duration_ms, sha256,
		       authorized_room_version, created_at_ms, updated_at_ms
		FROM room_media_upload_registrations
		WHERE app_code = ? AND expected_object_key = ? AND status = 'active'
		LIMIT 1`, appcode.FromContext(ctx), strings.TrimLeft(strings.TrimSpace(objectKey), "/"))
	registration, err := scanRoomMediaUploadRegistration(row)
	if errors.Is(err, sql.ErrNoRows) {
		return roomservice.RoomMediaUploadRegistration{}, false, nil
	}
	return registration, err == nil, err
}

// RegisterRoomMediaUpload 在首次授权通过后原子占用 command_id。
// 并发重复通过唯一主键收敛到首行，再逐字段比较；不同文件、用户或用途不能被 ON DUPLICATE 静默吞掉。
func (r *Repository) RegisterRoomMediaUpload(ctx context.Context, registration roomservice.RoomMediaUploadRegistration) (roomservice.RoomMediaUploadRegistration, error) {
	registration.AppCode = appcode.FromContext(ctx)
	registration.RoomID = strings.TrimSpace(registration.RoomID)
	registration.CommandID = strings.TrimSpace(registration.CommandID)
	registration.Purpose = strings.TrimSpace(registration.Purpose)
	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 room_media_upload_registrations (
		  app_code, room_id, command_id, actor_user_id, purpose, media_payload_sha256,
		  expected_object_key, object_url, status, content_type, media_format, size_bytes,
		  width, height, animated, frame_count, duration_ms, sha256,
		  authorized_room_version, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, '', 'authorized', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
		ON DUPLICATE KEY UPDATE command_id = room_media_upload_registrations.command_id`,
		registration.AppCode, registration.RoomID, registration.CommandID, registration.ActorUserID,
		registration.Purpose, 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.AuthorizedVersion,
		registration.CreatedAtMS, registration.UpdatedAtMS)
	if err != nil {
		return roomservice.RoomMediaUploadRegistration{}, err
	}
	saved, exists, err := r.GetRoomMediaUploadRegistration(ctx, registration.RoomID, registration.CommandID)
	if err != nil {
		return roomservice.RoomMediaUploadRegistration{}, err
	}
	if !exists || !sameRoomMediaUploadRegistration(saved, registration) {
		return roomservice.RoomMediaUploadRegistration{}, roomservice.ErrRoomMediaUploadCommandConflict
	}
	return saved, nil
}

// CompleteRoomMediaUpload 只把首次授权的 expected_object_key 推进为 active。
// URL 和完整媒体事实必须先由 service 与授权记录逐字段匹配；并发 complete 仍以首个 URL 为准。
func (r *Repository) CompleteRoomMediaUpload(ctx context.Context, registration roomservice.RoomMediaUploadRegistration) (roomservice.RoomMediaUploadRegistration, error) {
	registration.AppCode = appcode.FromContext(ctx)
	registration.RoomID = strings.TrimSpace(registration.RoomID)
	registration.CommandID = strings.TrimSpace(registration.CommandID)
	registration.ExpectedObjectKey = strings.TrimLeft(strings.TrimSpace(registration.ExpectedObjectKey), "/")
	registration.ObjectURL = strings.TrimSpace(registration.ObjectURL)
	registration.Status = "active"
	if registration.UpdatedAtMS <= 0 {
		registration.UpdatedAtMS = time.Now().UTC().UnixMilli()
	}
	_, err := r.db.ExecContext(ctx, `
		UPDATE room_media_upload_registrations
		SET object_url = ?, status = 'active', updated_at_ms = ?
		WHERE app_code = ? AND room_id = ? AND command_id = ? AND status = 'authorized'`,
		registration.ObjectURL, registration.UpdatedAtMS, registration.AppCode, registration.RoomID, registration.CommandID)
	if err != nil {
		return roomservice.RoomMediaUploadRegistration{}, err
	}
	saved, exists, err := r.GetRoomMediaUploadRegistration(ctx, registration.RoomID, registration.CommandID)
	if err != nil {
		return roomservice.RoomMediaUploadRegistration{}, err
	}
	if !exists || !sameRoomMediaUploadRegistration(saved, registration) ||
		saved.Status != "active" || strings.TrimSpace(saved.ObjectURL) != registration.ObjectURL {
		return roomservice.RoomMediaUploadRegistration{}, roomservice.ErrRoomMediaUploadCommandConflict
	}
	return saved, nil
}

func scanRoomMediaUploadRegistration(scanner interface{ Scan(dest ...any) error }) (roomservice.RoomMediaUploadRegistration, error) {
	var registration roomservice.RoomMediaUploadRegistration
	err := scanner.Scan(
		&registration.AppCode,
		&registration.RoomID,
		&registration.CommandID,
		&registration.ActorUserID,
		&registration.Purpose,
		&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.AuthorizedVersion,
		&registration.CreatedAtMS,
		&registration.UpdatedAtMS,
	)
	if err == nil {
		registration.Media.ObjectKey = registration.ExpectedObjectKey
		registration.Media.URL = registration.ObjectURL
		if registration.Status == "active" || registration.Status == "consumed" {
			// consumed 只改变一次性凭证生命周期；对象本身仍是已完成上传并可展示的 active 媒体。
			registration.Media.Status = "active"
		}
	}
	return registration, err
}

func sameRoomMediaUploadRegistration(left roomservice.RoomMediaUploadRegistration, right roomservice.RoomMediaUploadRegistration) bool {
	return appcode.Normalize(left.AppCode) == appcode.Normalize(right.AppCode) &&
		strings.TrimSpace(left.RoomID) == strings.TrimSpace(right.RoomID) &&
		strings.TrimSpace(left.CommandID) == strings.TrimSpace(right.CommandID) &&
		left.ActorUserID == right.ActorUserID &&
		strings.TrimSpace(left.Purpose) == strings.TrimSpace(right.Purpose) &&
		strings.EqualFold(strings.TrimSpace(left.MediaPayloadSHA256), strings.TrimSpace(right.MediaPayloadSHA256)) &&
		strings.TrimLeft(strings.TrimSpace(left.ExpectedObjectKey), "/") == strings.TrimLeft(strings.TrimSpace(right.ExpectedObjectKey), "/")
}
