package oauth2

import (
	"errors"
	"net/http"
	"net/url"

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

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

// InnerAuthorizationCodeCheckEndpoint godoc
//
//	@Summary		Check the authorization code request.
//	@Description	Check if the authorization code request is valid and return the application info.
//	@Tags			oauth2_authorization_code_inner
//	@Accept			json
//	@Produce		json
//	@Param			request	body		oauth2_dto.InnerAuthorizationCodeCheckRequest	true	"request"
//	@Success		200		{object}	oauth2_dto.InnerAuthorizationCodeResponse
//	@Failure		400		{object}	oauth2_dto.InnerAuthorizationCodeResponse
//	@Router			/api/v1/oauth/authorize/check  [post]
//
// https://datatracker.ietf.org/doc/html/rfc6749#autoid-36
func (o *Oauth2ControllerImpl) InnerAuthorizationCodeCheckEndpoint(c *gin.Context) {
	ctx := c.Request.Context()
	user, err := getAccountUser(c)
	if err != nil {
		abort(c, err)
		return
	}

	var req oauth2_dto.InnerAuthorizationCodeCheckRequest
	if err := autoBind(c, &req); err != nil {
		tilog.Info(ctx, "autoBind err", zap.Uint64("userId", user.Id), zap.String("clientID", req.ClientID), zap.Error(err))
		c.JSON(http.StatusBadRequest, createCodeRequestCheckErrorResponse(nil, errors.New("Bind Params Failed")))
		return
	}

	tilog.Info(ctx, "InnerAuthorizationCodeCheckEndpoint", zap.Uint64("userId", user.Id), zap.Any("req", req))
	if err := req.Validate(); err != nil {
		tilog.Info(ctx, "req validate ", zap.Uint64("userId", user.Id), zap.Error(err))
		c.JSON(http.StatusBadRequest, createCodeRequestCheckErrorResponse(nil, err))
		return
	}

	authorizeReq, err := o.oauth2Svc.InnerAuthorizationCodeRequestValidate(ctx, user, req.ClientID, req.ResponseType, req.RedirectURI, req.State, req.GetParsedScopes())
	if err != nil {
		tilog.Info(ctx, "InnerAuthorizationCodeRequestValidate", zap.Uint64("userId", user.Id), zap.String("clientID", req.ClientID), zap.Error(err))
		c.JSON(http.StatusBadRequest, createCodeRequestCheckErrorResponse(authorizeReq, err))
		return
	}
	c.JSON(http.StatusOK, createCodeRquestCheckSuccessResponse(authorizeReq))
}

func createCodeRequestCheckErrorResponse(req *oauth2_svc.AuthorizeRequest, err error) *oauth2_dto.InnerAuthorizationCodeResponse {
	resp := &oauth2_dto.InnerAuthorizationCodeResponse{
		InnerBasicResponse: oauth2_dto.InnerBasicResponse{
			Code:    int(tierrs.ErrCodeParams),
			Message: err.Error(),
		},
		NeedRedirect: false,
		RedirectTo:   "",
	}
	if req != nil && req.RedirectURI != nil {
		resp.NeedRedirect = true
		q := req.RedirectURI.Query()
		q.Set("error", err.Error())
		req.RedirectURI.RawQuery = q.Encode()
		resp.RedirectTo = req.RedirectURI.String()
	}
	return resp
}

func createCodeRquestCheckSuccessResponse(req *oauth2_svc.AuthorizeRequest) *oauth2_dto.InnerAuthorizationCodeResponse {
	app := req.Application
	return &oauth2_dto.InnerAuthorizationCodeResponse{
		InnerBasicResponse: oauth2_dto.InnerDefaultSuccessResponse(),
		NeedRedirect:       false,
		RedirectTo:         "",
		Application: &oauth2_dto.Application{
			Name:         app.Name,
			LogoURIDark:  app.LogoURIDark,
			LogoURILight: app.LogoURILight,
			HomePageURI:  app.HomePageURI,
		},
	}
}

// InnerAuthorizationCodeConfirmationEndpoint godoc
//
//	@Summary		Approve the authorization code grant request.
//	@Description	Approve the authorization code grant request and redirect to the redirect_uri.
//	@Tags			oauth2_authorization_code_inner
//	@Accept			json
//	@Produce		json
//	@Param			request	body		oauth2_dto.InnerAuthorizationCodeConfirmationRequest	true	"request"
//	@Success		200		{object}	oauth2_dto.InnerAuthorizationCodeResponse
//	@Failure		400		{object}	oauth2_dto.InnerAuthorizationCodeResponse
//	@Router			/api/v1/oauth/authorize/confirmation  [post]
//
// https://datatracker.ietf.org/doc/html/rfc6749#autoid-36
func (o *Oauth2ControllerImpl) InnerAuthorizationCodeConfirmationEndpoint(c *gin.Context) {
	ctx := c.Request.Context()
	user, err := getAccountUser(c)
	if err != nil {
		abort(c, err)
		return
	}

	var req oauth2_dto.InnerAuthorizationCodeConfirmationRequest
	if err := autoBind(c, &req); err != nil {
		tilog.Info(ctx, "autoBind err", zap.Uint64("userId", user.Id), zap.String("clientID", req.ClientID), zap.Error(err))
		c.JSON(http.StatusBadRequest, createCodeRequestCheckErrorResponse(nil, errors.New("Bind Params Failed")))
		return
	}

	tilog.Info(ctx, "InnerAuthorizationCodeConfirmationEndpoint", zap.Uint64("userId", user.Id), zap.Any("req", req))
	if err := req.Validate(); err != nil {
		tilog.Info(ctx, "req validate ", zap.Uint64("userId", user.Id), zap.Error(err))
		c.JSON(http.StatusBadRequest, createCodeRequestCheckErrorResponse(nil, err))
		return
	}

	authorizeReq, err := o.oauth2Svc.InnerAuthorizationCodeRequestValidate(ctx, user, req.ClientID, req.ResponseType, req.RedirectURI, req.State, req.GetParsedScopes())
	if err != nil {
		tilog.Info(ctx, "InnerAuthorizationCodeRequestValidate Error", zap.Uint64("userId", user.Id), zap.String("clientID", req.ClientID), zap.Error(err))
		c.JSON(http.StatusBadRequest, createCodeRequestCheckErrorResponse(authorizeReq, err))
		return
	}

	grant, redirectURL, err := o.oauth2Svc.InnerAuthorizationCodeApprove(ctx, user, authorizeReq, req.GetParsedOrgID(), req.GetParsedProjectIDs())
	if err != nil {
		tilog.Info(ctx, "InnerAuthorizationCodeApprove Error", zap.Uint64("userId", user.Id), zap.String("clientID", req.ClientID), zap.Error(err))
		c.JSON(http.StatusBadRequest, createCodeConfirmationErrorResponse(err))
		return
	}
	c.JSON(http.StatusOK, createCodeConfirmationSuccessResponse(grant, redirectURL, req.State))
}

func createCodeConfirmationErrorResponse(err error) *oauth2_dto.InnerAuthorizationCodeResponse {
	return &oauth2_dto.InnerAuthorizationCodeResponse{
		InnerBasicResponse: oauth2_dto.InnerBasicResponse{
			Code:    int(tierrs.ErrCodeParams),
			Message: err.Error(),
		},
		NeedRedirect: false,
		RedirectTo:   "",
	}
}

func createCodeConfirmationSuccessResponse(grant *entity.Oauth2CodeGrant, redirectURL *url.URL, state string) *oauth2_dto.InnerAuthorizationCodeResponse {
	q := redirectURL.Query()
	q.Set("code", grant.Code)
	if state != "" {
		q.Set("state", state)
	}
	redirectURL.RawQuery = q.Encode()
	return &oauth2_dto.InnerAuthorizationCodeResponse{
		InnerBasicResponse: oauth2_dto.InnerDefaultSuccessResponse(),
		NeedRedirect:       true,
		RedirectTo:         redirectURL.String(),
	}
}
