package dataapi

import (
	"context"
	"fmt"

	"go.uber.org/zap"

	"github.com/tidbcloud/auth-server/commons/utils"
	"github.com/tidbcloud/auth-server/idl/pbgen/proto/provider"
	"github.com/tidbcloud/auth-server/internal/dto"
	"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"
)

type ProviderController interface {
	provider.AuthServerServiceServer
}

type providerControllerImpl struct {
	provider.UnsafeAuthServerServiceServer

	oauth2Svc oauth2_svc.Oauth2Service
}

func NewProviderContoller(oauth2Svc oauth2_svc.Oauth2Service) ProviderController {
	return &providerControllerImpl{
		oauth2Svc: oauth2Svc,
	}
}

func (c *providerControllerImpl) GetIdentityByAccessToken(ctx context.Context, req *provider.GetIdentityByAccessTokenReq) (*provider.GetIdentityByAccessTokenResp, error) {
	if req.RawAccessToken == "" {
		tilog.Info(ctx, "raw_access_token is required")
		return createIdentityErrorResp(req.RawAccessToken, tierrs.NewBadRequest("raw_access_token is required", nil)), nil
	}

	akObj, err := c.oauth2Svc.GetAccessTokenIdentity(ctx, req.RawAccessToken)
	if err != nil {
		tilog.Info(ctx, "get access token identity failed", zap.Error(err))
		return createIdentityErrorResp(req.RawAccessToken, err), nil
	}

	identity, err2 := convertAccessTokenToIdentity(akObj, req.RawAccessToken)
	if err2 != nil {
		tilog.Error(ctx, "convert access token to identity failed", zap.Error(err))
		return createIdentityErrorResp(req.RawAccessToken, tierrs.NewInternalServerError("convert access token to identity failed", err)), nil
	}
	tilog.Info(ctx, "GetIdentityByAccessToken success"+fmt.Sprintf("token_id=%d, user_id=%d, org_id=%d,grant_all_projects=%b, project_ids=%v", identity.Id, identity.UserId, identity.OrgId, identity.ProjectIds, identity.GrantAllProjects))
	return &provider.GetIdentityByAccessTokenResp{
		AccessTokenIdentity: identity,
		BaseResp:            dto.DefaultSuccessBaseResp(),
	}, nil
}

func createIdentityErrorResp(rawAccessToken string, err tierrs.Error) *provider.GetIdentityByAccessTokenResp {
	return &provider.GetIdentityByAccessTokenResp{
		AccessTokenIdentity: &provider.AccessTokenIdentity{
			RawAccessToken: rawAccessToken,
		},
		BaseResp: tierrs.NewBaseRespFromTiErr(err),
	}
}

var GrantTypeMapToProto = map[entity.Oauth2GrantType]provider.GrantType{
	entity.Oauth2AuthorizationCode: provider.GrantType_AuthorizationCode,
	entity.Oauth2DeviceCode:        provider.GrantType_DeviceCode,
}

func convertAccessTokenToIdentity(akObj *entity.Oauth2AccessToken, rawAccessToken string) (*provider.AccessTokenIdentity, error) {
	// get grant type
	grantType, ok := GrantTypeMapToProto[akObj.GrantType]
	if !ok {
		return nil, fmt.Errorf("invalid grant type: %v", akObj.GrantType)
	}
	return &provider.AccessTokenIdentity{
		Id:               uint64(akObj.ID),
		UserId:           akObj.UserID,
		UserEmail:        akObj.UserEmail,
		OrgId:            akObj.OrgID,
		GrantAllProjects: akObj.GrantAllProjects,
		ProjectIds:       akObj.ProjectIDs.IDs,
		CreatedAt:        utils.ToTimestamp(akObj.CreatedAt),
		ExpiresIn:        int64(akObj.AccessTokenExpiresIn),
		RawAccessToken:   rawAccessToken,
		GrantType:        grantType,
	}, nil
}
