// Package auth 实现 user-service 的认证、三方登录、密码登录和 refresh session 用例。
package auth

import (
	"context"
	"strings"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	authdomain "hyapp/services/user-service/internal/domain/auth"
)

// Meta 是 gateway 透传到 user-service 的审计元信息。
type Meta struct {
	// AppCode 是 gateway 已解析的 App 租户键。
	AppCode string
	// RequestID 是 gateway 透传的请求 ID，用于登录审计。
	RequestID string
	// ClientIP 是客户端 IP。
	ClientIP string
	// UserAgent 是客户端 UA。
	UserAgent string
	// CountryByIP 是 gateway 或可信边缘层根据入口 IP 得到的国家快照。
	CountryByIP string
	// Platform 是客户端平台快照，认证链路只做审计和风险任务透传。
	Platform string
	// Language 是客户端语言偏好，gateway 只用作弱信号快拦。
	Language string
	// Timezone 是客户端 IANA 时区，gateway 只用作弱信号快拦。
	Timezone string
}

func (s *Service) audit(ctx context.Context, meta Meta, userID int64, loginType string, provider string, result string, failureCode string) {
	s.recordAudit(ctx, authdomain.LoginAudit{
		AppCode:       appcode.Normalize(meta.AppCode),
		RequestID:     meta.RequestID,
		UserID:        userID,
		LoginType:     loginType,
		Provider:      provider,
		Channel:       auditChannel(loginType, provider),
		Platform:      normalizePlatform(meta.Platform),
		Result:        result,
		FailureCode:   failureCode,
		ClientIP:      meta.ClientIP,
		IPCountryCode: normalizeCountryCode(meta.CountryByIP),
		UserAgent:     meta.UserAgent,
		CreatedAtMs:   s.now().UnixMilli(),
	})
}

func (s *Service) recordAudit(ctx context.Context, audit authdomain.LoginAudit) {
	if s.authRepository == nil {
		// 没有 repository 时无法写审计，但不应让辅助审计路径触发 panic。
		return
	}

	// 审计失败不影响登录主链路，所以这里有意忽略错误。
	_ = s.authRepository.RecordLoginAudit(ctx, audit)
}

// RecordLoginBlocked 写入 gateway fast guard 拒绝的登录审计；失败不应影响 gateway 返回阻断响应。
func (s *Service) RecordLoginBlocked(ctx context.Context, meta Meta, channel string, platform string, language string, timezone string, blockReason string) bool {
	if s.authRepository == nil {
		return false
	}
	nowMs := s.now().UnixMilli()
	audit := authdomain.LoginAudit{
		AppCode:       appcode.Normalize(meta.AppCode),
		RequestID:     meta.RequestID,
		LoginType:     blockedLoginType(channel),
		Provider:      blockedProvider(channel),
		Channel:       normalizeChannel(channel),
		Platform:      normalizePlatform(firstNonEmpty(platform, meta.Platform)),
		Result:        "blocked",
		FailureCode:   string(xerr.AuthLoginBlocked),
		ClientIP:      meta.ClientIP,
		IPCountryCode: normalizeCountryCode(meta.CountryByIP),
		Blocked:       true,
		BlockReason:   strings.TrimSpace(blockReason),
		UserAgent:     meta.UserAgent,
		CreatedAtMs:   nowMs,
	}
	if audit.Channel == "" {
		audit.Channel = "account"
	}
	return s.authRepository.RecordLoginAudit(ctx, audit) == nil
}

func auditChannel(loginType string, provider string) string {
	if loginType == loginPassword {
		return "account"
	}
	if loginType == loginThird {
		return normalizeThirdPartyChannel(provider)
	}
	return strings.TrimSpace(loginType)
}

func blockedLoginType(channel string) string {
	if normalizeChannel(channel) == "account" {
		return loginPassword
	}
	return loginThird
}

func blockedProvider(channel string) string {
	channel = normalizeChannel(channel)
	if channel == "account" {
		return ""
	}
	return channel
}

func normalizeThirdPartyChannel(provider string) string {
	provider = strings.ToLower(strings.TrimSpace(provider))
	switch provider {
	case "google", "google.com", "firebase":
		return "google"
	case "facebook", "facebook.com":
		return "facebook"
	case "twitter", "x":
		return "twitter"
	case "tiktok":
		return "tiktok"
	default:
		return provider
	}
}

func normalizeChannel(channel string) string {
	channel = strings.ToLower(strings.TrimSpace(channel))
	switch channel {
	case "", "password":
		return "account"
	case "third_party":
		return ""
	default:
		return normalizeThirdPartyChannel(channel)
	}
}

func normalizePlatform(platform string) string {
	platform = strings.ToLower(strings.TrimSpace(platform))
	if platform == "android" || platform == "ios" {
		return platform
	}
	return platform
}

func normalizeCountryCode(country string) string {
	country = strings.ToUpper(strings.TrimSpace(country))
	if len(country) < 2 || len(country) > 3 {
		return ""
	}
	return country
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return value
		}
	}
	return ""
}
