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

import (
	"context"
	"log/slog"
	"strings"

	"hyapp/pkg/appcode"
	"hyapp/pkg/logx"
	"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
	// RefreshRequestID 是客户端为一次 refresh 尝试生成的稳定幂等键；网络重试必须复用，普通链路为空。
	RefreshRequestID string
	// ClientIP 是客户端 IP。
	ClientIP string
	// UserAgent 是客户端 UA。
	UserAgent string
	// CountryByIP 是 gateway 或可信边缘层根据入口 IP 得到的国家快照。
	CountryByIP string
	// Platform 是客户端平台快照，认证链路只做审计和风险任务透传。
	Platform string
	// AppVersion 是本次登录客户端版本；只记录本次请求，不能沿用用户注册时的历史版本。
	AppVersion string
	// BuildNumber 是本次登录客户端构建号；与 AppVersion 一起用于精确定位客户端包。
	BuildNumber string
	// Language 是客户端语言偏好，gateway 只用作弱信号快拦。
	Language string
	// Timezone 是客户端 IANA 时区，gateway 只用作弱信号快拦。
	Timezone string
	// DeviceModel 是客户端上报的设备型号快照（例如 Redmi Note 8 Pro）；只进设备档案，不参与认证判定。
	DeviceModel string
	// DeviceManufacturer 是设备厂商快照（例如 Xiaomi/Apple）。
	DeviceManufacturer string
	// OSVersion 是客户端系统版本快照（例如 Android 13 的 "13"）。
	OSVersion string
	// IMEI 是客户端可获取时上报的设备 IMEI；Android 10+ 无特权权限拿不到，预期常为空。
	IMEI 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),
		AppVersion:    normalizeAuditClientVersion(meta.AppVersion),
		BuildNumber:   normalizeAuditClientVersion(meta.BuildNumber),
		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
	}

	// 审计失败不影响登录主链路，但必须暴露日志；否则漏执行迁移或数据库瞬断会让版本画像静默停留在旧值。
	if err := s.authRepository.RecordLoginAudit(ctx, audit); err != nil {
		logx.Error(
			logx.With(ctx, slog.String("request_id", audit.RequestID), slog.String("app_code", audit.AppCode)),
			"login_audit_write_failed",
			err,
			slog.String("component", "user_auth"),
			slog.String("login_type", audit.LoginType),
			slog.Int64("user_id", audit.UserID),
		)
	}
}

// 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)),
		AppVersion:    normalizeAuditClientVersion(meta.AppVersion),
		BuildNumber:   normalizeAuditClientVersion(meta.BuildNumber),
		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 normalizeAuditClientVersion(value string) string {
	value = strings.TrimSpace(value)
	runes := []rune(value)
	if len(runes) <= 64 {
		return value
	}
	// 客户端 header 不受 proto 长度约束。审计元数据超长时截断而不是让 INSERT 失败，
	// 否则登录虽成功却丢失整条审计，Dashboard 也无法确定该用户最近一次成功登录。
	return string(runes[:64])
}

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 ""
}
