// Copyright (C) 2024 veypi <i@veypi.com>
// 2025-03-04 16:08:06
// Distributed under terms of the MIT license.

package oauth

import (
	"time"

	"github.com/google/uuid"
	"github.com/veypi/vbase/cfg"
	"github.com/veypi/vbase/libs/cache"
	"github.com/veypi/vbase/models"
	"github.com/veypi/vigo"
)

type TokenRequest struct {
	GrantType    string  `json:"grant_type" src:"form" desc:"授权类型"`
	Code         *string `json:"code,omitempty" src:"form" desc:"授权码"`
	RefreshToken *string `json:"refresh_token,omitempty" src:"form" desc:"刷新令牌"`
	ClientID     string  `json:"client_id" src:"form" desc:"客户端ID"`
	ClientSecret string  `json:"client_secret" src:"form" desc:"客户端密钥"`
}

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	RefreshToken string `json:"refresh_token,omitempty"`
	Scope        string `json:"scope,omitempty"`
}

func token(x *vigo.X, req *TokenRequest) (*TokenResponse, error) {
	switch req.GrantType {
	case "authorization_code":
		return handleAuthorizationCode(req)
	case "refresh_token":
		return handleRefreshToken(req)
	default:
		return nil, vigo.ErrInvalidArg.WithString("unsupported grant type")
	}
}

func handleAuthorizationCode(req *TokenRequest) (*TokenResponse, error) {
	// 验证客户端
	var client models.OAuthClient
	if err := cfg.DB().First(&client, "client_id = ?", req.ClientID).Error; err != nil {
		return nil, vigo.ErrUnauthorized.WithString("invalid client")
	}

	// 验证授权码
	if req.Code == nil {
		return nil, vigo.ErrInvalidArg.WithString("code is required for authorization_code grant")
	}
	code := *req.Code
	var authData map[string]any
	if err := cache.GetObject(cache.OAuthCodeKey(code), &authData); err != nil {
		return nil, vigo.ErrUnauthorized.WithString("invalid or expired code")
	}

	// 删除已使用的授权码
	cache.Delete(cache.OAuthCodeKey(code))

	userID := authData["user_id"].(string)
	scope := authData["scope"].(string)

	// 生成访问令牌
	accessToken := uuid.New().String()
	refreshToken := uuid.New().String()
	expiresAt := time.Now().Add(time.Hour)

	// 保存令牌
	token := &models.OAuthToken{
		ClientID:     req.ClientID,
		UserID:       userID,
		AccessToken:  accessToken,
		RefreshToken: refreshToken,
		Scope:        scope,
		ExpiresAt:    expiresAt,
	}
	if err := cfg.DB().Create(token).Error; err != nil {
		return nil, vigo.ErrInternalServer.WithError(err)
	}

	return &TokenResponse{
		AccessToken:  accessToken,
		TokenType:    "Bearer",
		ExpiresIn:    int(time.Hour.Seconds()),
		RefreshToken: refreshToken,
		Scope:        scope,
	}, nil
}

func handleRefreshToken(req *TokenRequest) (*TokenResponse, error) {
	// 查找刷新令牌
	if req.RefreshToken == nil {
		return nil, vigo.ErrInvalidArg.WithString("refresh_token is required for refresh_token grant")
	}
	var token models.OAuthToken
	if err := cfg.DB().First(&token, "refresh_token = ?", *req.RefreshToken).Error; err != nil {
		return nil, vigo.ErrTokenInvalid
	}

	if token.Revoked {
		return nil, vigo.ErrTokenInvalid
	}

	// 生成新的访问令牌
	accessToken := uuid.New().String()
	newRefreshToken := uuid.New().String()
	expiresAt := time.Now().Add(time.Hour)

	// 使旧令牌失效
	cfg.DB().Model(&token).Updates(map[string]any{
		"revoked": true,
	})

	// 保存新令牌
	newToken := &models.OAuthToken{
		ClientID:     token.ClientID,
		UserID:       token.UserID,
		AccessToken:  accessToken,
		RefreshToken: newRefreshToken,
		Scope:        token.Scope,
		ExpiresAt:    expiresAt,
	}
	if err := cfg.DB().Create(newToken).Error; err != nil {
		return nil, vigo.ErrInternalServer.WithError(err)
	}

	return &TokenResponse{
		AccessToken:  accessToken,
		TokenType:    "Bearer",
		ExpiresIn:    int(time.Hour.Seconds()),
		RefreshToken: newRefreshToken,
		Scope:        token.Scope,
	}, nil
}
