package auth

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	jwt "github.com/golang-jwt/jwt/v5"
	"hyapp/pkg/appcode"
)

type contextKey string

const (
	userContextKey   contextKey = "gateway_user_id"
	claimsContextKey contextKey = "gateway_claims"
)

// Claims 是 gateway 从 access token 中消费的最小用户状态快照。
// 它只用于入口鉴权和 profile gate，不替代 user-service 的用户主数据。
type Claims struct {
	AppCode                string
	UserID                 int64
	SessionID              string
	ProfileCompleted       bool
	OnboardingStatus       string
	AccessTokenExpiresAtMS int64
}

// Verifier 负责在 gateway 入口层校验用户 JWT。
type Verifier struct {
	secret []byte
}

// NewVerifier 初始化入口 JWT 校验器。
func NewVerifier(secret string) *Verifier {
	return &Verifier{secret: []byte(secret)}
}

// VerifyUserID 只负责从 Authorization header 中提取并校验用户身份。
// HTTP 状态码和响应 envelope 属于 transport 层，auth 包不直接写 ResponseWriter。
func (v *Verifier) VerifyUserID(header string) (int64, error) {
	claims, err := v.Verify(header)
	if err != nil {
		return 0, err
	}

	return claims.UserID, nil
}

// Verify 校验 access token 并返回 gateway 入口需要的用户状态快照。
func (v *Verifier) Verify(header string) (Claims, error) {
	if !strings.HasPrefix(header, "Bearer ") {
		return Claims{}, fmt.Errorf("missing bearer token")
	}

	tokenString := strings.TrimPrefix(header, "Bearer ")
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
		if token.Method.Alg() != jwt.SigningMethodHS256.Alg() {
			return nil, fmt.Errorf("unexpected signing method")
		}

		return v.secret, nil
	})
	if err != nil || !token.Valid {
		return Claims{}, fmt.Errorf("invalid token")
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return Claims{}, fmt.Errorf("invalid claims")
	}

	rawUserID, ok := claims["user_id"].(string)
	if !ok || strings.TrimSpace(rawUserID) == "" {
		return Claims{}, fmt.Errorf("missing user_id")
	}
	userID, err := strconv.ParseInt(strings.TrimSpace(rawUserID), 10, 64)
	if err != nil || userID <= 0 {
		return Claims{}, fmt.Errorf("invalid user_id")
	}

	profileCompleted, _ := claims["profile_completed"].(bool)
	onboardingStatus, _ := claims["onboarding_status"].(string)
	sessionID, _ := claims["sid"].(string)
	appCode, _ := claims["app_code"].(string)
	expiresAtMS := claimUnixSecondsMS(claims["exp"])

	return Claims{
		AppCode:                appcode.Normalize(appCode),
		UserID:                 userID,
		SessionID:              strings.TrimSpace(sessionID),
		ProfileCompleted:       profileCompleted,
		OnboardingStatus:       strings.TrimSpace(onboardingStatus),
		AccessTokenExpiresAtMS: expiresAtMS,
	}, nil
}

func claimUnixSecondsMS(value any) int64 {
	switch typed := value.(type) {
	case float64:
		if typed <= 0 {
			return 0
		}
		return int64(typed) * 1000
	case int64:
		if typed <= 0 {
			return 0
		}
		return typed * 1000
	case int:
		if typed <= 0 {
			return 0
		}
		return int64(typed) * 1000
	case string:
		parsed, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 64)
		if err != nil || parsed <= 0 {
			return 0
		}
		return parsed * 1000
	default:
		return 0
	}
}

// WithUserID 把已鉴权用户写入请求上下文，供 gateway 组装 RequestMeta。
func WithUserID(ctx context.Context, userID int64) context.Context {
	return context.WithValue(ctx, userContextKey, userID)
}

// WithClaims 把 access token 中的准入快照写入上下文。
func WithClaims(ctx context.Context, claims Claims) context.Context {
	ctx = appcode.WithContext(ctx, claims.AppCode)
	ctx = WithUserID(ctx, claims.UserID)
	return context.WithValue(ctx, claimsContextKey, claims)
}

// UserIDFromContext 提取鉴权后的用户 ID。
func UserIDFromContext(ctx context.Context) int64 {
	userID, _ := ctx.Value(userContextKey).(int64)

	return userID
}

// SessionIDFromContext 提取 access token 中的服务端 session_id。
func SessionIDFromContext(ctx context.Context) string {
	claims, _ := ctx.Value(claimsContextKey).(Claims)
	return strings.TrimSpace(claims.SessionID)
}

// ProfileCompletedFromContext 返回 gateway profile gate 的判断依据。
func ProfileCompletedFromContext(ctx context.Context) bool {
	claims, _ := ctx.Value(claimsContextKey).(Claims)
	return claims.ProfileCompleted
}

// OnboardingStatusFromContext 暴露 token 中的注册状态，便于后续审计或灰度判断。
func OnboardingStatusFromContext(ctx context.Context) string {
	claims, _ := ctx.Value(claimsContextKey).(Claims)
	return claims.OnboardingStatus
}

// AccessTokenExpiresAtMSFromContext 返回当前 access token 的过期时间；旧测试 token 没有 exp 时返回 0。
func AccessTokenExpiresAtMSFromContext(ctx context.Context) int64 {
	claims, _ := ctx.Value(claimsContextKey).(Claims)
	return claims.AccessTokenExpiresAtMS
}
