package appuser

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

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

type AppLoginLog struct {
	ID              int64  `json:"id"`
	RequestID       string `json:"requestId"`
	UserID          string `json:"userId"`
	DisplayUserID   string `json:"displayUserId"`
	Username        string `json:"username"`
	Avatar          string `json:"avatar"`
	Country         string `json:"country"`
	RegionID        int64  `json:"regionId"`
	RegionName      string `json:"regionName"`
	LoginIP         string `json:"loginIp"`
	IPCountryCode   string `json:"ipCountryCode"`
	Channel         string `json:"channel"`
	Platform        string `json:"platform"`
	Result          string `json:"result"`
	FailureCode     string `json:"failureCode"`
	Blocked         bool   `json:"blocked"`
	BlockReason     string `json:"blockReason"`
	RiskHighlighted bool   `json:"riskHighlighted"`
	LoginType       string `json:"loginType"`
	Provider        string `json:"provider"`
	CreatedAtMs     int64  `json:"createdAtMs"`
}

func (s *Service) ListLoginLogs(ctx context.Context, query loginLogQuery) ([]AppLoginLog, int64, error) {
	if s.userDB == nil {
		return nil, 0, fmt.Errorf("user mysql is not configured")
	}
	query = normalizeLoginLogQuery(query)
	whereSQL, args := loginLogWhereSQL(ctx, query)
	total, err := countRows(ctx, s.userDB, whereSQL, args...)
	if err != nil {
		return nil, 0, err
	}
	rows, err := s.userDB.QueryContext(ctx, fmt.Sprintf(`
		SELECT la.id,
		       la.request_id,
		       COALESCE(la.user_id, 0),
		       COALESCE(u.current_display_user_id, ''),
		       COALESCE(u.username, ''),
		       COALESCE(u.avatar, ''),
		       COALESCE(u.country, ''),
		       COALESCE(u.region_id, 0),
		       CASE
		         WHEN COALESCE(u.region_id, 0) = 0 THEN 'GLOBAL'
		         ELSE COALESCE((SELECT rg.name FROM regions rg WHERE rg.app_code = u.app_code AND rg.region_id = u.region_id LIMIT 1), '')
		       END,
		       COALESCE(la.client_ip, ''),
		       COALESCE(la.ip_country_code, ''),
		       COALESCE(NULLIF(la.channel, ''), CASE WHEN la.login_type = 'password' THEN 'account' ELSE COALESCE(la.provider, '') END),
		       COALESCE(la.platform, ''),
		       la.result,
		       COALESCE(la.failure_code, ''),
		       la.blocked,
		       la.block_reason,
		       la.login_type,
		       COALESCE(la.provider, ''),
		       la.created_at_ms
		%s
		ORDER BY la.created_at_ms DESC, la.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([]AppLoginLog, 0, query.PageSize)
	for rows.Next() {
		item, err := scanAppLoginLog(rows)
		if err != nil {
			return nil, 0, err
		}
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		return nil, 0, err
	}
	return items, total, nil
}

func loginLogWhereSQL(ctx context.Context, query loginLogQuery) (string, []any) {
	whereSQL := `
		FROM login_audit la
		LEFT JOIN users u ON u.app_code = la.app_code AND u.user_id = la.user_id
		WHERE la.app_code = ?`
	args := []any{appctx.FromContext(ctx)}
	nowMs := nowMillis()
	if query.UserID > 0 {
		whereSQL += " AND la.user_id = ?"
		args = append(args, query.UserID)
	}
	if query.DisplayUserID != "" {
		matchSQL, matchArgs := shared.UserDisplayIDSQL("u", query.DisplayUserID, nowMs)
		whereSQL += " AND " + matchSQL
		args = append(args, matchArgs...)
	}
	if !query.UserFilter.IsEmpty() {
		matchSQL, matchArgs := shared.UserIdentityFieldsSQL("u", "la.user_id", query.UserFilter, nowMs)
		whereSQL += " AND " + matchSQL
		args = append(args, matchArgs...)
	}
	if query.IP != "" {
		whereSQL += " AND la.client_ip = ?"
		args = append(args, query.IP)
	}
	if query.IPCountryCode != "" {
		whereSQL += " AND la.ip_country_code = ?"
		args = append(args, query.IPCountryCode)
	}
	if query.Channel != "" {
		whereSQL += " AND la.channel = ?"
		args = append(args, query.Channel)
	}
	if query.Platform != "" {
		whereSQL += " AND la.platform = ?"
		args = append(args, query.Platform)
	}
	if query.RegionIDSet {
		whereSQL += " AND COALESCE(u.region_id, 0) = ?"
		args = append(args, query.RegionID)
	}
	switch query.Blocked {
	case "blocked":
		whereSQL += " AND (la.blocked = 1 OR la.failure_code = 'AUTH_LOGIN_BLOCKED')"
	case "not_blocked":
		whereSQL += " AND la.blocked = 0 AND COALESCE(la.failure_code, '') <> 'AUTH_LOGIN_BLOCKED'"
	}
	if query.Result != "" {
		whereSQL += " AND la.result = ?"
		args = append(args, query.Result)
	}
	if query.StartMs > 0 {
		whereSQL += " AND la.created_at_ms >= ?"
		args = append(args, query.StartMs)
	}
	if query.EndMs > 0 {
		whereSQL += " AND la.created_at_ms <= ?"
		args = append(args, query.EndMs)
	}
	if query.Keyword != "" && query.UserFilter.IsEmpty() {
		like := "%" + query.Keyword + "%"
		matchSQL, matchArgs := shared.UserIdentityKeywordSQL("u", "la.user_id", query.Keyword, nowMs)
		whereSQL += " AND (" + matchSQL + " OR la.client_ip LIKE ?)"
		args = append(args, matchArgs...)
		args = append(args, like)
	}
	return whereSQL, args
}

func scanAppLoginLog(rows *sql.Rows) (AppLoginLog, error) {
	var item AppLoginLog
	var userID int64
	if err := rows.Scan(
		&item.ID,
		&item.RequestID,
		&userID,
		&item.DisplayUserID,
		&item.Username,
		&item.Avatar,
		&item.Country,
		&item.RegionID,
		&item.RegionName,
		&item.LoginIP,
		&item.IPCountryCode,
		&item.Channel,
		&item.Platform,
		&item.Result,
		&item.FailureCode,
		&item.Blocked,
		&item.BlockReason,
		&item.LoginType,
		&item.Provider,
		&item.CreatedAtMs,
	); err != nil {
		return AppLoginLog{}, err
	}
	if userID > 0 {
		item.UserID = strconv.FormatInt(userID, 10)
	}
	item.Result = normalizeLoginLogResult(item.Result, item.FailureCode, item.Blocked)
	item.RiskHighlighted = item.Blocked || item.FailureCode == "AUTH_LOGIN_BLOCKED"
	return item, nil
}

func normalizeLoginLogResult(result string, failureCode string, blocked bool) string {
	result = strings.TrimSpace(result)
	if result != "" {
		return result
	}
	if blocked || failureCode == "AUTH_LOGIN_BLOCKED" {
		return "blocked"
	}
	return "failed"
}

func normalizeLoginLogQuery(query loginLogQuery) loginLogQuery {
	if query.Page < 1 {
		query.Page = 1
	}
	if query.PageSize < 1 {
		query.PageSize = 20
	}
	if query.PageSize > 100 {
		query.PageSize = 100
	}
	query.Keyword = strings.TrimSpace(query.Keyword)
	query.DisplayUserID = strings.TrimSpace(query.DisplayUserID)
	query.UserFilter.UserID = strings.TrimSpace(query.UserFilter.UserID)
	query.UserFilter.DisplayUserID = strings.TrimSpace(query.UserFilter.DisplayUserID)
	query.UserFilter.Username = strings.TrimSpace(query.UserFilter.Username)
	query.IP = strings.TrimSpace(query.IP)
	query.IPCountryCode = normalizeCountryCode(query.IPCountryCode)
	query.Channel = strings.ToLower(strings.TrimSpace(query.Channel))
	query.Platform = strings.ToLower(strings.TrimSpace(query.Platform))
	query.Blocked = strings.ToLower(strings.TrimSpace(query.Blocked))
	query.Result = strings.ToLower(strings.TrimSpace(query.Result))
	if query.Result == "blocked" {
		query.Blocked = "blocked"
		query.Result = ""
	}
	if query.Blocked != "blocked" && query.Blocked != "not_blocked" {
		query.Blocked = "all"
	}
	switch query.Result {
	case "", "failed", "revoked", "success":
	default:
		query.Result = ""
	}
	return query
}
