package host

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

	"hyapp/pkg/xerr"
	hostdomain "hyapp/services/user-service/internal/domain/host"
)

const privateMessageHistoryMaxHours = 7 * 24

// PreparePrivateMessageHistoryHours 只向小时状态小表补齐最近七天的主键，不更新已完成行。
// 多个 app_code 的 cron 并发调用会由 INSERT IGNORE 和共享 SDKAppID 主键自然收敛。
func (r *Repository) PreparePrivateMessageHistoryHours(ctx context.Context, sdkAppID int64, fromHourMS int64, throughHourMS int64, nowMS int64) error {
	if r == nil || r.db == nil {
		return xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	if sdkAppID <= 0 || fromHourMS <= 0 || throughHourMS < fromHourMS {
		return xerr.New(xerr.InvalidArgument, "private message history range is invalid")
	}
	const hourMS = int64(time.Hour / time.Millisecond)
	hourCount := int((throughHourMS-fromHourMS)/hourMS) + 1
	if hourCount <= 0 || hourCount > privateMessageHistoryMaxHours {
		return xerr.New(xerr.InvalidArgument, "private message history range exceeds seven days")
	}

	values := make([]string, 0, hourCount)
	args := make([]any, 0, hourCount*4)
	for hourStartMS := fromHourMS; hourStartMS <= throughHourMS; hourStartMS += hourMS {
		values = append(values, "(?, ?, 'pending', ?, ?)")
		args = append(args, sdkAppID, hourStartMS, nowMS, nowMS)
	}
	_, err := r.db.ExecContext(ctx, `
		INSERT IGNORE INTO user_private_message_history_sync (
		  sdk_app_id, hour_start_ms, status, created_at_ms, updated_at_ms
		) VALUES `+strings.Join(values, ","), args...)
	return err
}

// ClaimPrivateMessageHistoryHour 原子认领最新的可处理小时，让当天 Host Center 数据优先恢复。
// 状态表最多保留最近七天新增的小时行，ORDER BY/LIMIT 不会触碰消息事实大表。
func (r *Repository) ClaimPrivateMessageHistoryHour(ctx context.Context, sdkAppID int64, runID string, fromHourMS int64, throughHourMS int64, nowMS int64, lockTTL time.Duration) (int64, int, bool, error) {
	if r == nil || r.db == nil {
		return 0, 0, false, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	runID = strings.TrimSpace(runID)
	if sdkAppID <= 0 || runID == "" || fromHourMS <= 0 || throughHourMS < fromHourMS {
		return 0, 0, false, xerr.New(xerr.InvalidArgument, "private message history claim is invalid")
	}
	if lockTTL <= 0 {
		lockTTL = 5 * time.Minute
	}
	lockedUntilMS := nowMS + lockTTL.Milliseconds()
	result, err := r.db.ExecContext(ctx, `
		UPDATE user_private_message_history_sync
		SET status = 'processing',
		    attempts = attempts + 1,
		    locked_by = ?,
		    locked_until_ms = ?,
		    updated_at_ms = ?
		WHERE sdk_app_id = ?
		  AND hour_start_ms >= ? AND hour_start_ms <= ?
		  AND (
		    (status IN ('pending', 'retry') AND next_retry_at_ms <= ?)
		    OR (status = 'processing' AND locked_until_ms <= ?)
		  )
		ORDER BY hour_start_ms DESC
		LIMIT 1`,
		runID, lockedUntilMS, nowMS,
		sdkAppID, fromHourMS, throughHourMS, nowMS, nowMS,
	)
	if err != nil {
		return 0, 0, false, err
	}
	affected, err := result.RowsAffected()
	if err != nil || affected == 0 {
		return 0, 0, false, err
	}

	var hourStartMS int64
	var attempts int
	err = r.db.QueryRowContext(ctx, `
		SELECT hour_start_ms, attempts
		FROM user_private_message_history_sync
		WHERE sdk_app_id = ? AND status = 'processing' AND locked_by = ? AND locked_until_ms = ?
		LIMIT 1`,
		sdkAppID, runID, lockedUntilMS,
	).Scan(&hourStartMS, &attempts)
	if err != nil {
		if err == sql.ErrNoRows {
			return 0, 0, false, xerr.New(xerr.Conflict, "private message history claim was lost")
		}
		return 0, 0, false, err
	}
	return hourStartMS, attempts, true, nil
}

// ImportPrivateMessageEvents 批量把腾讯归档转换为最小统计事实。users 主键分别验证收发双方，
// app_code 取接收者主数据且要求双方同租户；host_profiles 联接避免保存与 Host Center 无关的普通私聊。
func (r *Repository) ImportPrivateMessageEvents(ctx context.Context, events []hostdomain.PrivateMessageEvent) (int, error) {
	if r == nil || r.db == nil {
		return 0, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	if len(events) == 0 {
		return 0, nil
	}
	if len(events) > 500 {
		return 0, xerr.New(xerr.InvalidArgument, "private message history import batch exceeds 500")
	}

	createdAtMS := time.Now().UTC().UnixMilli()
	selects := make([]string, 0, len(events))
	args := make([]any, 0, len(events)*5)
	for index, event := range events {
		prefix := "SELECT"
		if index > 0 {
			prefix = "UNION ALL SELECT"
		}
		selects = append(selects, prefix+" ? AS event_id, ? AS sender_user_id, ? AS target_user_id, ? AS occurred_at_ms, ? AS created_at_ms")
		args = append(args, event.EventID, event.SenderUserID, event.TargetUserID, event.OccurredAtMS, createdAtMS)
	}
	query := `
		INSERT IGNORE INTO user_private_message_events (
		  app_code, event_id, sender_user_id, target_user_id, occurred_at_ms, created_at_ms
		)
		SELECT target.app_code,
		       imported.event_id,
		       sender.user_id,
		       target.user_id,
		       imported.occurred_at_ms,
		       imported.created_at_ms
		FROM (` + strings.Join(selects, "\n") + `) imported
		INNER JOIN users target
		  ON target.user_id = imported.target_user_id
		INNER JOIN users sender
		  ON sender.user_id = imported.sender_user_id AND sender.app_code = target.app_code
		INNER JOIN host_profiles host
		  ON host.app_code = target.app_code AND host.user_id = target.user_id`

	result, err := r.db.ExecContext(ctx, query, args...)
	if err != nil {
		return 0, fmt.Errorf("import private message history events: %w", err)
	}
	affected, err := result.RowsAffected()
	return int(affected), err
}

// CompletePrivateMessageHistoryHour 仅允许当前 run 完成自己的租约，防止超时旧 worker
// 覆盖新 worker 的重试结果。
func (r *Repository) CompletePrivateMessageHistoryHour(ctx context.Context, sdkAppID int64, hourStartMS int64, runID string, sourceMessages int, importedEvents int, nowMS int64) error {
	result, err := r.db.ExecContext(ctx, `
		UPDATE user_private_message_history_sync
		SET status = 'completed',
		    source_message_count = ?,
		    imported_event_count = ?,
		    next_retry_at_ms = 0,
		    locked_by = '',
		    locked_until_ms = 0,
		    last_error = '',
		    updated_at_ms = ?
		WHERE sdk_app_id = ? AND hour_start_ms = ? AND status = 'processing' AND locked_by = ?`,
		sourceMessages, importedEvents, nowMS, sdkAppID, hourStartMS, strings.TrimSpace(runID),
	)
	if err != nil {
		return err
	}
	affected, err := result.RowsAffected()
	if err != nil {
		return err
	}
	if affected == 0 {
		return xerr.New(xerr.Conflict, "private message history completion lease was lost")
	}
	return nil
}

// RetryPrivateMessageHistoryHour 记录脱敏错误并延后重试；下载 URL 和 UserSig 已在 REST
// 边界被剥离，last_error 仍限制长度，避免外部响应放大状态表。
func (r *Repository) RetryPrivateMessageHistoryHour(ctx context.Context, sdkAppID int64, hourStartMS int64, runID string, retryAtMS int64, errorMessage string, nowMS int64) error {
	errorMessage = strings.TrimSpace(errorMessage)
	if len(errorMessage) > 512 {
		errorMessage = errorMessage[:512]
	}
	result, err := r.db.ExecContext(ctx, `
		UPDATE user_private_message_history_sync
		SET status = 'retry',
		    next_retry_at_ms = ?,
		    locked_by = '',
		    locked_until_ms = 0,
		    last_error = ?,
		    updated_at_ms = ?
		WHERE sdk_app_id = ? AND hour_start_ms = ? AND status = 'processing' AND locked_by = ?`,
		retryAtMS, errorMessage, nowMS, sdkAppID, hourStartMS, strings.TrimSpace(runID),
	)
	if err != nil {
		return err
	}
	affected, err := result.RowsAffected()
	if err != nil {
		return err
	}
	if affected == 0 {
		return xerr.New(xerr.Conflict, "private message history retry lease was lost")
	}
	return nil
}
