package host

import (
	"context"
	"time"

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

const validMicDayThresholdMS = int64(time.Hour / time.Millisecond)

// GetHostEngagementStats 聚合 user-service 自己拥有的事实表，不从当前 Agency 关系反推历史归属。
func (r *Repository) GetHostEngagementStats(ctx context.Context, query hostdomain.EngagementStatsQuery) (hostdomain.EngagementStats, error) {
	if r == nil || r.db == nil {
		return hostdomain.EngagementStats{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	stats := hostdomain.EngagementStats{}
	appCode := appcode.FromContext(ctx)
	startDate := time.UnixMilli(query.StartAtMS).UTC().Format("2006-01-02")
	endDate := time.UnixMilli(query.EndAtMS - 1).UTC().Format("2006-01-02")

	if err := r.db.QueryRowContext(ctx, `
		SELECT COALESCE(SUM(mic_online_ms), 0),
		       COALESCE(SUM(CASE WHEN mic_online_ms >= ? THEN 1 ELSE 0 END), 0)
		FROM user_mic_daily_stats
		WHERE app_code = ? AND user_id = ? AND stat_date BETWEEN ? AND ?`,
		validMicDayThresholdMS, appCode, query.HostUserID, startDate, endDate,
	).Scan(&stats.ValidMicDurationMS, &stats.ValidMicDays); err != nil {
		return hostdomain.EngagementStats{}, err
	}

	// Fami 上线前的 C2C 回调曾把事实写入默认租户；读取按两个精确 app_code 索引区间
	// 合并，并用发送者主数据校验真实租户，既恢复历史私信人数又不把 Lalu 用户串入 Fami。
	privateMessageSQL := `
		SELECT COUNT(DISTINCT matched.sender_user_id)
		FROM (
		  SELECT sender_user_id
		  FROM user_private_message_events FORCE INDEX (idx_private_message_target_time)
		  WHERE app_code = ? AND target_user_id = ?
		    AND occurred_at_ms >= ? AND occurred_at_ms < ?
		) matched
		INNER JOIN users sender
		  ON sender.user_id = matched.sender_user_id AND sender.app_code = ?`
	privateMessageArgs := []any{appCode, query.HostUserID, query.StartAtMS, query.EndAtMS, appCode}
	if appCode != appcode.Default {
		// UNION ALL 保持两个复合索引范围独立可用；最终 DISTINCT 收敛迁移前后重复发送者。
		privateMessageSQL = `
			SELECT COUNT(DISTINCT matched.sender_user_id)
			FROM (
			  SELECT sender_user_id
			  FROM user_private_message_events FORCE INDEX (idx_private_message_target_time)
			  WHERE app_code = ? AND target_user_id = ?
			    AND occurred_at_ms >= ? AND occurred_at_ms < ?
			  UNION ALL
			  SELECT sender_user_id
			  FROM user_private_message_events FORCE INDEX (idx_private_message_target_time)
			  WHERE app_code = ? AND target_user_id = ?
			    AND occurred_at_ms >= ? AND occurred_at_ms < ?
			) matched
			INNER JOIN users sender
			  ON sender.user_id = matched.sender_user_id AND sender.app_code = ?`
		privateMessageArgs = []any{
			appCode, query.HostUserID, query.StartAtMS, query.EndAtMS,
			appcode.Default, query.HostUserID, query.StartAtMS, query.EndAtMS,
			appCode,
		}
	}
	if err := r.db.QueryRowContext(ctx, privateMessageSQL, privateMessageArgs...).Scan(&stats.PrivateMessageSenders); err != nil {
		return hostdomain.EngagementStats{}, err
	}

	if err := r.db.QueryRowContext(ctx, `
		SELECT COALESCE(SUM(delta), 0)
		FROM user_follow_events
		WHERE app_code = ? AND followee_user_id = ? AND occurred_at_ms >= ? AND occurred_at_ms < ?`,
		appCode, query.HostUserID, query.StartAtMS, query.EndAtMS,
	).Scan(&stats.NewFollowers); err != nil {
		return hostdomain.EngagementStats{}, err
	}

	onlineMS, err := r.hostOnlineDuration(ctx, query)
	if err != nil {
		return hostdomain.EngagementStats{}, err
	}
	stats.OnlineDurationMS = onlineMS
	return stats, nil
}

// hostOnlineDuration 按每天“最早会话开始到最后一次心跳”累计；多设备/多 session 不重复相加。
func (r *Repository) hostOnlineDuration(ctx context.Context, query hostdomain.EngagementStatsQuery) (int64, error) {
	rows, err := r.db.QueryContext(ctx, `
		SELECT created_at_ms, last_heartbeat_at_ms
		FROM auth_sessions
		WHERE app_code = ? AND user_id = ?
		  AND last_heartbeat_at_ms >= ? AND created_at_ms < ?`,
		appcode.FromContext(ctx), query.HostUserID, query.StartAtMS, query.EndAtMS,
	)
	if err != nil {
		return 0, err
	}
	defer rows.Close()

	type daySpan struct{ first, last int64 }
	spans := map[string]daySpan{}
	for rows.Next() {
		var startedAtMS, heartbeatAtMS int64
		if err := rows.Scan(&startedAtMS, &heartbeatAtMS); err != nil {
			return 0, err
		}
		start := maxInt64(startedAtMS, query.StartAtMS)
		end := minInt64(heartbeatAtMS, query.EndAtMS-1)
		if end < start {
			continue
		}
		for day := utcDayStart(start); day <= utcDayStart(end); day += int64(24 * time.Hour / time.Millisecond) {
			segmentStart := maxInt64(start, day)
			segmentEnd := minInt64(end, day+int64(24*time.Hour/time.Millisecond)-1)
			key := time.UnixMilli(day).UTC().Format("2006-01-02")
			span, exists := spans[key]
			if !exists || segmentStart < span.first {
				span.first = segmentStart
			}
			if !exists || segmentEnd > span.last {
				span.last = segmentEnd
			}
			spans[key] = span
		}
	}
	if err := rows.Err(); err != nil {
		return 0, err
	}
	var total int64
	for _, span := range spans {
		if span.last >= span.first {
			total += span.last - span.first
		}
	}
	return total, nil
}

// RecordPrivateMessageEvent 由 users 主数据反查租户，不信任公网回调缺省的 app_code。
// users.user_id 是全局主键；两次主键查找同时确认发送者和接收者属于同一 App，避免 Fami 私信误落到默认租户。
func (r *Repository) RecordPrivateMessageEvent(ctx context.Context, event hostdomain.PrivateMessageEvent) (bool, error) {
	result, err := r.db.ExecContext(ctx, `
		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, ?, sender.user_id, target.user_id, ?, ?
		FROM users target
		INNER JOIN users sender
		  ON sender.user_id = ? AND sender.app_code = target.app_code
		WHERE target.user_id = ?`,
		event.EventID, event.OccurredAtMS, time.Now().UTC().UnixMilli(), event.SenderUserID, event.TargetUserID,
	)
	if err != nil {
		return false, err
	}
	rows, err := result.RowsAffected()
	return rows > 0, err
}

func utcDayStart(value int64) int64 {
	t := time.UnixMilli(value).UTC()
	return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC).UnixMilli()
}

func minInt64(left, right int64) int64 {
	if left < right {
		return left
	}
	return right
}

func maxInt64(left, right int64) int64 {
	if left > right {
		return left
	}
	return right
}
