package dashboard

import (
	"context"
	"fmt"
	"strings"
	"time"

	"hyapp-admin-server/internal/appctx"
)

type UserProfileOverviewQuery struct {
	AppCode string
	StatTZ  string
	StartMS int64
	EndMS   int64
}

type UserProfileDistributionItem struct {
	Key   string `json:"key"`
	Label string `json:"label"`
	Count int64  `json:"count"`
}

type UserProfileOverview struct {
	AppCode                string                        `json:"appCode"`
	TotalUsers             int64                         `json:"totalUsers"`
	NewUsers               int64                         `json:"newUsers"`
	ActiveUsers            int64                         `json:"activeUsers"`
	GenderDistribution     []UserProfileDistributionItem `json:"genderDistribution"`
	AppVersionDistribution []UserProfileDistributionItem `json:"appVersionDistribution"`
	UpdatedAtMS            int64                         `json:"updatedAtMs"`
}

func (s *DashboardService) UserProfileOverview(ctx context.Context, query UserProfileOverviewQuery) (UserProfileOverview, error) {
	if s == nil || s.userDB == nil {
		return UserProfileOverview{}, fmt.Errorf("user mysql is not configured")
	}
	query.AppCode = appctx.Normalize(query.AppCode)
	totalUsers, genderDistribution, err := s.queryNaturalUserGenderDistribution(ctx, query.AppCode)
	if err != nil {
		return UserProfileOverview{}, fmt.Errorf("query dashboard user profile: %w", err)
	}

	// 新增和 DAU 必须沿用数据大屏的事件聚合口径；直接按 users.created_at_ms 或登录表重算会与
	// StatisticsOverview 的 UserRegistered/活跃事实去重规则产生双重口径。
	statisticsOverview, err := s.StatisticsOverview(ctx, StatisticsQuery{
		AppCode: query.AppCode,
		StatTZ:  query.StatTZ,
		StartMS: query.StartMS,
		EndMS:   query.EndMS,
	})
	if err != nil {
		return UserProfileOverview{}, fmt.Errorf("query dashboard daily statistics: %w", err)
	}
	newUsers := anyInt64(statisticsOverview["new_users"])
	activeUsers := anyInt64(statisticsOverview["active_users"])

	versionDistribution, err := s.queryNaturalUserAppVersionDistribution(ctx, query.AppCode)
	if err != nil {
		return UserProfileOverview{}, fmt.Errorf("query dashboard app version distribution: %w", err)
	}

	return UserProfileOverview{
		AppCode:                query.AppCode,
		TotalUsers:             totalUsers,
		NewUsers:               newUsers,
		ActiveUsers:            activeUsers,
		GenderDistribution:     genderDistribution,
		AppVersionDistribution: versionDistribution,
		UpdatedAtMS:            time.Now().UTC().UnixMilli(),
	}, nil
}

func (s *DashboardService) queryNaturalUserGenderDistribution(ctx context.Context, appCode string) (int64, []UserProfileDistributionItem, error) {
	rows, err := s.userDB.QueryContext(ctx, `
		SELECT CASE
		         WHEN TRIM(COALESCE(gender, '')) = '' THEN 'unknown'
		         ELSE LOWER(TRIM(gender))
		       END AS gender_key,
		       COUNT(*) AS user_count
		FROM users
		WHERE app_code = ?
		  AND profile_completed = 1
		  AND LOWER(TRIM(COALESCE(source, ''))) NOT IN ('game_robot', 'quick_account')
		GROUP BY gender_key
		ORDER BY user_count DESC, gender_key ASC`, appCode)
	if err != nil {
		return 0, nil, err
	}
	defer rows.Close()

	// 总人数直接求和同一条性别分组结果，让 totalUsers 与饼图永远共享 profile_completed/source 条件；
	// 分开执行 COUNT 和 GROUP BY 容易在以后新增“真实用户”排除条件时只改到其中一处。
	items := make([]UserProfileDistributionItem, 0)
	var total int64
	for rows.Next() {
		var key string
		var count int64
		if err := rows.Scan(&key, &count); err != nil {
			return 0, nil, err
		}
		key = normalizeDistributionKey(key)
		items = append(items, UserProfileDistributionItem{Key: key, Label: genderDistributionLabel(key), Count: count})
		total += count
	}
	if err := rows.Err(); err != nil {
		return 0, nil, err
	}
	return total, items, nil
}

func (s *DashboardService) queryNaturalUserAppVersionDistribution(ctx context.Context, appCode string) ([]UserProfileDistributionItem, error) {
	// login_audit 的复合索引把 login_type 放在 created_at_ms 之前；若把三种登录类型写成 IN，
	// MySQL 必须为每个用户扫描并排序全部候选，线上百万级审计表会超时。这里按类型拆成三个等值索引
	// 范围，每个范围只取一条，再对最多三条候选按 (created_at_ms, id) 选最新记录，既保持原口径，
	// 也避免窗口函数物化整张审计表。版本过滤必须放在 latest 选定之后：若先过滤审计行，最新登录
	// 没带版本的用户会错误回退到旧版本。natural 是 MySQL 保留字，用户别名必须使用 natural_user。
	rows, err := s.userDB.QueryContext(ctx, `
		SELECT TRIM(latest.app_version) AS version_key,
		       COUNT(*) AS user_count
		FROM users AS natural_user
		LEFT JOIN LATERAL (
			SELECT candidate.app_version
			FROM (
				(SELECT audit.app_version, audit.created_at_ms, audit.id
				 FROM login_audit AS audit
				 WHERE audit.app_code = natural_user.app_code
				   AND audit.user_id = natural_user.user_id
				   AND audit.result = 'success'
				   AND audit.blocked = 0
				   AND audit.login_type = 'password'
				 ORDER BY audit.created_at_ms DESC, audit.id DESC
				 LIMIT 1)
				UNION ALL
				(SELECT audit.app_version, audit.created_at_ms, audit.id
				 FROM login_audit AS audit
				 WHERE audit.app_code = natural_user.app_code
				   AND audit.user_id = natural_user.user_id
				   AND audit.result = 'success'
				   AND audit.blocked = 0
				   AND audit.login_type = 'third_party'
				 ORDER BY audit.created_at_ms DESC, audit.id DESC
				 LIMIT 1)
				UNION ALL
				(SELECT audit.app_version, audit.created_at_ms, audit.id
				 FROM login_audit AS audit
				 WHERE audit.app_code = natural_user.app_code
				   AND audit.user_id = natural_user.user_id
				   AND audit.result = 'success'
				   AND audit.blocked = 0
				   AND audit.login_type = 'refresh'
				 ORDER BY audit.created_at_ms DESC, audit.id DESC
				 LIMIT 1)
			) AS candidate
			ORDER BY candidate.created_at_ms DESC, candidate.id DESC
			LIMIT 1
		) AS latest ON TRUE
		WHERE natural_user.app_code = ?
		  AND natural_user.profile_completed = 1
		  AND LOWER(TRIM(COALESCE(natural_user.source, ''))) NOT IN ('game_robot', 'quick_account')
		  AND latest.app_version IS NOT NULL
		  AND TRIM(latest.app_version) <> ''
		  AND LOWER(TRIM(latest.app_version)) NOT IN ('unknown', '<nil>')
		GROUP BY version_key
		ORDER BY user_count DESC, version_key ASC`, appCode)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	items := make([]UserProfileDistributionItem, 0)
	for rows.Next() {
		var key string
		var count int64
		if err := rows.Scan(&key, &count); err != nil {
			return nil, err
		}
		key = normalizeDistributionKey(key)
		// SQL 已排除所有无版本用户；这里仍守住响应边界，避免测试替身或历史脏值把“未知版本”带回前端。
		if key == "unknown" {
			continue
		}
		items = append(items, UserProfileDistributionItem{Key: key, Label: key, Count: count})
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

func normalizeDistributionKey(value string) string {
	value = strings.TrimSpace(value)
	if value == "" || strings.EqualFold(value, "unknown") || value == "<nil>" {
		return "unknown"
	}
	return value
}

func genderDistributionLabel(key string) string {
	switch strings.ToLower(key) {
	case "male":
		return "男"
	case "female":
		return "女"
	case "non_binary", "nonbinary":
		return "非二元"
	case "unknown":
		return "未知"
	default:
		return key
	}
}
