package oauth2

import (
	"errors"
	"fmt"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"

	"github.com/tidbcloud/auth-server/commons/errs"
	oauth2_dto "github.com/tidbcloud/auth-server/internal/dto/oauth2"
	oauth2_svc "github.com/tidbcloud/auth-server/internal/service/oauth2"
	"github.com/tidbcloud/tiinf/monitor/tilog"
)

// TokenEndpoint godoc
//
//	@Summary		Token endpoint.
//	@Description	OAuth2 token endpoint, support device code grant and refresh token grant.
//	@Tags			oauth2_public
//	@Accept			json,x-www-form-urlencoded
//	@Produce		json
//	@Param			device_flow			body		oauth2_dto.DeviceTokenRequest				true	"device_flow"
//	@Param			refresh_token		body		oauth2_dto.RefreshTokenRequest				true	"refresh_token"
//	@Param			authorization_code	body		oauth2_dto.AuthorizationCodeTokenRequest	true	"authorization_code"
//	@Success		200					{object}	oauth2_dto.TokenResponse
//	@Failure		400					{object}	oauth2_dto.ErrorResponse
//	@Router			/v1/token [post]
//
// It supports the following grant types:
// - Device Code Grant  https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
// - Refresh Token Grant https://datatracker.ietf.org/doc/html/rfc6749#section-6
// - Authorization Code Grant [TODO]
func (o *Oauth2ControllerImpl) TokenEndpoint(c *gin.Context) {
	ctx := c.Request.Context()
	var req oauth2_dto.TokenRequest
	if err := autoBind(c, &req); err != nil {
		tilog.Info(ctx, "autoBind err", zap.Error(err))
		responseWithBadRequest(c, errors.New("bad request body"))
		return
	}
	if err := req.Validate(); err != nil {
		tilog.Info(ctx, "get token req  Validate", zap.Error(err))
		responseWithBadRequest(c, err)
		return
	}

	switch req.GrantType {
	case oauth2_svc.DeviceCodeGrantType:
		if err := req.ValidateDeviceGrant(); err != nil {
			tilog.Info(ctx, "get token req  ValidateDeviceGrant", zap.Error(err))
			responseWithBadRequest(c, err)
			return
		}
		resp, err := o.oauth2Svc.HandleDeviceTokenRequest(ctx, req.ClientID, req.DeviceCode)
		tilog.Info(ctx, "HandleDeviceTokenRequest result="+fmt.Sprintf("%t", err == nil))
		if err != nil {
			tilog.Info(ctx, "get device token response err", zap.Error(err))
			c.JSON(errs.GetStatusCode(err), oauth2_dto.ErrorResponse{
				Error: err.Error(),
			})
			return
		}
		c.JSON(http.StatusOK, resp)
	case oauth2_svc.AuthorizationCodeGrantType:
		if err := req.ValidateCodeGrant(); err != nil {
			tilog.Info(ctx, "get token req  ValidateCodeGrant", zap.Error(err))
			responseWithBadRequest(c, err)
			return
		}
		resp, err := o.oauth2Svc.CreateAccessTokenByCodeGrant(ctx, req.ClientID, req.ClientSecret, req.Code, req.RedirectURI)
		if err != nil {
			tilog.Info(ctx, "get code token response err", zap.Error(err))
			c.JSON(errs.GetStatusCode(err), oauth2_dto.ErrorResponse{
				Error: err.Error(),
			})
			return
		}
		c.JSON(http.StatusOK, resp)
	case oauth2_svc.RefreshTokenGrantType:
		if err := req.ValidateRefreshToken(); err != nil {
			tilog.Info(ctx, "refresh token req  ValidateRefreshToken", zap.Error(err))
			responseWithBadRequest(c, err)
			return
		}
		resp, err := o.oauth2Svc.HandleRefreshTokenRequest(ctx, req.ClientID, req.ClientSecret, req.RefreshToken)
		tilog.Info(ctx, "HandleRefreshTokenRequest result="+fmt.Sprintf("%t", err == nil))
		if err != nil {
			tilog.Info(ctx, "refresh token response err", zap.Error(err))
			c.JSON(errs.GetStatusCode(err), oauth2_dto.ErrorResponse{
				Error: err.Error(),
			})
			return
		}
		c.JSON(http.StatusOK, resp)
	default:
		// TODO: support other grant types
		c.JSON(http.StatusBadRequest, oauth2_dto.ErrorResponse{
			Error: errs.ErrOauth2UnsupportedGrantType.Error(),
		})
		return
	}
}

// RevokeEndpoint godoc
//
//	@Summary		Revoke token endpoint.
//	@Description	OAuth2 revoke token endpoint.
//	@Tags			oauth2_public
//	@Accept			json,x-www-form-urlencoded
//	@Produce		json
//	@Param			token	body		oauth2_dto.RevokeTokenRequest	true	"token"
//	@Success		200		{object}	nil
//	@Failure		400		{object}	oauth2_dto.ErrorResponse
//	@Router			/v1/revoke [post]
//
// https://datatracker.ietf.org/doc/html/rfc7009#section-2.1
func (o *Oauth2ControllerImpl) RevokeEndpoint(c *gin.Context) {
	ctx := c.Request.Context()
	var req oauth2_dto.RevokeTokenRequest
	if err := autoBind(c, &req); err != nil {
		responseWithBadRequest(c, errors.New("bad request body"))
		return
	}
	if err := req.Validate(); err != nil {
		responseWithBadRequest(c, err)
		return
	}
	o.oauth2Svc.HandleRevokeTokenRequest(ctx, req.ClientID, req.ClientSecret, req.Token, req.TokenTypeHint)
	c.String(http.StatusOK, "")
}

// GetUserInfoEndpoint godoc
//
//	@Summary		Get userinfo endpoint.
//	@Description	OAuth2 get userinfo endpoint.
//	@Tags			oauth2_public
//	@Accept			json,x-www-form-urlencoded
//	@Produce		json
//	@Param			Authorization	header		string	true	"Bearer token"
//	@Success		200				{object}	oauth2_dto.UserInfo
//	@Failure		400				{object}	oauth2_dto.ErrorResponse
//	@Router			/v1/userinfo [get]
func (o *Oauth2ControllerImpl) GetUserInfoEndpoint(c *gin.Context) {
	ctx := c.Request.Context()
	authHeader := c.Request.Header.Get("Authorization")

	if authHeader == "" {
		responseWithBadRequest(c, errors.New("Authorization header missing"))
		return
	}
	if !strings.HasPrefix(authHeader, "Bearer ") {
		responseWithBadRequest(c, errors.New("invalid authorization header"))
		return
	}

	accessToken := strings.TrimPrefix(authHeader, "Bearer ")
	if accessToken == "" {
		responseWithBadRequest(c, errors.New("bad request header"))
		return
	}

	userinfo, err := o.oauth2Svc.GetUserInfo(ctx, accessToken)
	if err != nil {
		c.JSON(errs.GetStatusCode(err), oauth2_dto.ErrorResponse{
			Error: err.Error(),
		})

		return
	}

	c.JSON(http.StatusOK, userinfo)
}

// GetAccessTokenInfoEndpoint godoc
//
//	@Summary		Get authInfo endpoint.
//	@Description	OAuth2 get authorization info endpoint.
//	@Tags			oauth2_public
//	@Accept			json,x-www-form-urlencoded
//	@Produce		json
//	@Param			Authorization	header		string	true	"Bearer token"
//	@Success		200				{object}	oauth2_dto.AccessTokenInfo
//	@Failure		400				{object}	oauth2_dto.ErrorResponse
//	@Router			/v1/authInfo [get]
func (o *Oauth2ControllerImpl) GetAccessTokenInfoEndpoint(c *gin.Context) {
	ctx := c.Request.Context()
	authHeader := c.Request.Header.Get("Authorization")

	if authHeader == "" {
		responseWithBadRequest(c, errors.New("Authorization header missing"))
		return
	}
	if !strings.HasPrefix(authHeader, "Bearer ") {
		responseWithBadRequest(c, errors.New("invalid authorization header"))
		return
	}

	accessToken := strings.TrimPrefix(authHeader, "Bearer ")
	if accessToken == "" {
		responseWithBadRequest(c, errors.New("bad request header"))
		return
	}

	userinfo, err := o.oauth2Svc.GetAccessTokenInfo(ctx, accessToken)
	if err != nil {
		c.JSON(errs.GetStatusCode(err), oauth2_dto.ErrorResponse{
			Error: err.Error(),
		})
		//responseWithBadRequest(c, err)
		return
	}

	c.JSON(http.StatusOK, userinfo)
}
