package appuser

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

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

type AppUserBanRecord struct {
	ID          int64        `json:"id"`
	Target      AppUserBrief `json:"target"`
	Operator    BanOperator  `json:"operator"`
	OldStatus   string       `json:"oldStatus"`
	NewStatus   string       `json:"newStatus"`
	Reason      string       `json:"reason"`
	RequestID   string       `json:"requestId"`
	CreatedAtMs int64        `json:"createdAtMs"`
}

type AppUserBrief struct {
	Avatar        string `json:"avatar"`
	DisplayUserID string `json:"displayUserId"`
	UserID        string `json:"userId"`
	Username      string `json:"username"`
}

type BanOperator struct {
	Type          string `json:"type"`
	AdminID       string `json:"adminId,omitempty"`
	Account       string `json:"account,omitempty"`
	Avatar        string `json:"avatar,omitempty"`
	DisplayUserID string `json:"displayUserId,omitempty"`
	Name          string `json:"name,omitempty"`
	UserID        string `json:"userId,omitempty"`
}

type adminOperatorProfile struct {
	Account string
	Name    string
}

func (s *Service) ListBannedUsers(ctx context.Context, query banListQuery) ([]AppUserBanRecord, int64, error) {
	if s.userDB == nil {
		return nil, 0, fmt.Errorf("user mysql is not configured")
	}
	query = normalizeBanListQuery(query)
	adminIDs, err := s.lookupAdminOperatorIDs(ctx, query.operatorAdminLookupKeyword())
	if err != nil {
		return nil, 0, err
	}
	whereSQL, args := banListWhereSQL(ctx, query, adminIDs)
	total, err := countRows(ctx, s.userDB, whereSQL, args...)
	if err != nil {
		return nil, 0, err
	}
	rows, err := s.userDB.QueryContext(ctx, fmt.Sprintf(`
		SELECT l.id,
		       l.target_user_id,
		       target.current_display_user_id,
		       COALESCE(target.username, ''),
		       COALESCE(target.avatar, ''),
		       l.old_status,
		       l.new_status,
		       l.operator_type,
		       l.operator_user_id,
		       COALESCE(operator_user.current_display_user_id, ''),
		       COALESCE(operator_user.username, ''),
		       COALESCE(operator_user.avatar, ''),
		       l.reason,
		       l.request_id,
		       l.created_at_ms
		%s
		ORDER BY l.created_at_ms DESC, l.id DESC
		LIMIT ? OFFSET ?
	`, whereSQL), append(args, query.PageSize, offset(query.Page, query.PageSize))...)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	items := make([]AppUserBanRecord, 0, query.PageSize)
	adminOperatorIDs := make([]int64, 0)
	for rows.Next() {
		item, adminOperatorID, err := scanBanRecord(rows)
		if err != nil {
			return nil, 0, err
		}
		if adminOperatorID > 0 {
			adminOperatorIDs = append(adminOperatorIDs, adminOperatorID)
		}
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		return nil, 0, err
	}
	if err := s.fillAdminOperators(ctx, items, adminOperatorIDs); err != nil {
		return nil, 0, err
	}
	return items, total, nil
}

func banListWhereSQL(ctx context.Context, query banListQuery, adminIDs []int64) (string, []any) {
	appCode := appctx.FromContext(ctx)
	whereSQL := `
		FROM user_status_change_logs l
		INNER JOIN (
			SELECT app_code, target_user_id, MAX(id) AS latest_id
			FROM user_status_change_logs
			WHERE app_code = ? AND new_status IN ('banned', 'disabled')
			GROUP BY app_code, target_user_id
		) latest ON latest.app_code = l.app_code AND latest.latest_id = l.id
		INNER JOIN users target ON target.app_code = l.app_code AND target.user_id = l.target_user_id
		LEFT JOIN users operator_user ON l.operator_type = 'app_user' AND operator_user.app_code = l.app_code AND operator_user.user_id = l.operator_user_id
		WHERE l.app_code = ? AND target.status IN ('banned', 'disabled')`
	args := []any{appCode, appCode}
	nowMs := nowMillis()
	if !query.TargetFilter.IsEmpty() {
		matchSQL, matchArgs := shared.UserIdentityFieldsSQL("target", "target.user_id", query.TargetFilter, nowMs)
		whereSQL += " AND " + matchSQL
		args = append(args, matchArgs...)
	} else if query.TargetKeyword != "" {
		matchSQL, matchArgs := shared.UserIdentityKeywordSQL("target", "target.user_id", query.TargetKeyword, nowMs)
		whereSQL += " AND " + matchSQL
		args = append(args, matchArgs...)
	}
	if !query.OperatorFilter.IsEmpty() {
		matchSQL, matchArgs := shared.UserIdentityFieldsSQL("operator_user", "operator_user.user_id", query.OperatorFilter, nowMs)
		whereSQL += " AND (l.operator_type = 'app_user' AND " + matchSQL + ")"
		args = append(args, matchArgs...)
	} else if query.OperatorKeyword != "" {
		matchSQL, matchArgs := shared.UserIdentityKeywordSQL("operator_user", "operator_user.user_id", query.OperatorKeyword, nowMs)
		operatorClauses := []string{"(l.operator_type = 'app_user' AND " + matchSQL + ")"}
		args = append(args, matchArgs...)
		if len(adminIDs) > 0 {
			operatorClauses = append(operatorClauses, "(l.operator_type = 'admin' AND l.operator_user_id IN ("+placeholders(len(adminIDs))+"))")
			for _, id := range adminIDs {
				args = append(args, id)
			}
		}
		whereSQL += " AND (" + strings.Join(operatorClauses, " OR ") + ")"
	} else if query.OperatorAdminKeyword != "" {
		if len(adminIDs) == 0 {
			whereSQL += " AND 1 = 0"
		} else {
			whereSQL += " AND (l.operator_type = 'admin' AND l.operator_user_id IN (" + placeholders(len(adminIDs)) + "))"
			for _, id := range adminIDs {
				args = append(args, id)
			}
		}
	}
	if query.StartMs > 0 {
		whereSQL += " AND l.created_at_ms >= ?"
		args = append(args, query.StartMs)
	}
	if query.EndMs > 0 {
		whereSQL += " AND l.created_at_ms < ?"
		args = append(args, query.EndMs)
	}
	return whereSQL, args
}

func scanBanRecord(rows *sql.Rows) (AppUserBanRecord, int64, error) {
	var item AppUserBanRecord
	var targetUserID int64
	var operatorType string
	var operatorUserID int64
	var operatorDisplayUserID string
	var operatorUsername string
	var operatorAvatar string
	if err := rows.Scan(
		&item.ID,
		&targetUserID,
		&item.Target.DisplayUserID,
		&item.Target.Username,
		&item.Target.Avatar,
		&item.OldStatus,
		&item.NewStatus,
		&operatorType,
		&operatorUserID,
		&operatorDisplayUserID,
		&operatorUsername,
		&operatorAvatar,
		&item.Reason,
		&item.RequestID,
		&item.CreatedAtMs,
	); err != nil {
		return AppUserBanRecord{}, 0, err
	}
	item.Target.UserID = strconv.FormatInt(targetUserID, 10)
	item.Operator.Type = operatorType
	if operatorType == "admin" {
		item.Operator.AdminID = strconv.FormatInt(operatorUserID, 10)
		item.Operator.Account = item.Operator.AdminID
		return item, operatorUserID, nil
	}
	if operatorType == "app_user" && operatorUserID > 0 {
		item.Operator.UserID = strconv.FormatInt(operatorUserID, 10)
		item.Operator.DisplayUserID = operatorDisplayUserID
		item.Operator.Name = operatorUsername
		item.Operator.Avatar = operatorAvatar
	}
	return item, 0, nil
}

func (s *Service) lookupAdminOperatorIDs(ctx context.Context, keyword string) ([]int64, error) {
	if s.adminDB == nil || strings.TrimSpace(keyword) == "" {
		return nil, nil
	}
	like := "%" + strings.TrimSpace(keyword) + "%"
	rows, err := s.adminDB.QueryContext(ctx, `
		SELECT id
		FROM admin_users
		WHERE CAST(id AS CHAR) LIKE ? OR username LIKE ? OR name LIKE ?
		LIMIT 200
	`, like, like, like)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	ids := make([]int64, 0)
	for rows.Next() {
		var id int64
		if err := rows.Scan(&id); err != nil {
			return nil, err
		}
		ids = append(ids, id)
	}
	return ids, rows.Err()
}

func (s *Service) fillAdminOperators(ctx context.Context, items []AppUserBanRecord, adminIDs []int64) error {
	if s.adminDB == nil || len(adminIDs) == 0 {
		return nil
	}
	ids := uniquePositiveInt64s(adminIDs)
	if len(ids) == 0 {
		return nil
	}
	args := make([]any, 0, len(ids))
	for _, id := range ids {
		args = append(args, id)
	}
	rows, err := s.adminDB.QueryContext(ctx, fmt.Sprintf(`
		SELECT id, username, name
		FROM admin_users
		WHERE id IN (%s)
	`, placeholders(len(ids))), args...)
	if err != nil {
		return err
	}
	defer rows.Close()
	profiles := make(map[int64]adminOperatorProfile, len(ids))
	for rows.Next() {
		var id int64
		var profile adminOperatorProfile
		if err := rows.Scan(&id, &profile.Account, &profile.Name); err != nil {
			return err
		}
		profiles[id] = profile
	}
	if err := rows.Err(); err != nil {
		return err
	}
	for index := range items {
		if items[index].Operator.Type != "admin" {
			continue
		}
		adminID, _ := strconv.ParseInt(items[index].Operator.AdminID, 10, 64)
		profile, ok := profiles[adminID]
		if !ok {
			continue
		}
		items[index].Operator.Account = profile.Account
		items[index].Operator.Name = profile.Name
	}
	return nil
}

func normalizeBanListQuery(query banListQuery) banListQuery {
	if query.Page < 1 {
		query.Page = 1
	}
	if query.PageSize < 1 {
		query.PageSize = 20
	}
	if query.PageSize > 100 {
		query.PageSize = 100
	}
	query.TargetKeyword = strings.TrimSpace(query.TargetKeyword)
	query.OperatorKeyword = strings.TrimSpace(query.OperatorKeyword)
	query.OperatorAdminKeyword = strings.TrimSpace(query.OperatorAdminKeyword)
	query.TargetFilter.UserID = strings.TrimSpace(query.TargetFilter.UserID)
	query.TargetFilter.DisplayUserID = strings.TrimSpace(query.TargetFilter.DisplayUserID)
	query.TargetFilter.Username = strings.TrimSpace(query.TargetFilter.Username)
	query.OperatorFilter.UserID = strings.TrimSpace(query.OperatorFilter.UserID)
	query.OperatorFilter.DisplayUserID = strings.TrimSpace(query.OperatorFilter.DisplayUserID)
	query.OperatorFilter.Username = strings.TrimSpace(query.OperatorFilter.Username)
	return query
}

func (query banListQuery) operatorAdminLookupKeyword() string {
	if keyword := strings.TrimSpace(query.OperatorAdminKeyword); keyword != "" {
		return keyword
	}
	return strings.TrimSpace(query.OperatorKeyword)
}

func uniquePositiveInt64s(values []int64) []int64 {
	seen := make(map[int64]struct{}, len(values))
	out := make([]int64, 0, len(values))
	for _, value := range values {
		if value <= 0 {
			continue
		}
		if _, ok := seen[value]; ok {
			continue
		}
		seen[value] = struct{}{}
		out = append(out, value)
	}
	return out
}

func placeholders(count int) string {
	if count <= 0 {
		return ""
	}
	return strings.TrimRight(strings.Repeat("?,", count), ",")
}
