package provider_test

import (
	"context"
	"testing"
	"time"

	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"

	"github.com/tidbcloud/auth-server/commons/utils"
	"github.com/tidbcloud/auth-server/idl/pbgen/proto/provider"
	provider_ctl "github.com/tidbcloud/auth-server/internal/controller/provider"
	"github.com/tidbcloud/auth-server/internal/entity"
	oauth2_svc "github.com/tidbcloud/auth-server/internal/service/oauth2"
	tierrs "github.com/tidbcloud/tiinf/errs"
)

func TestGetIdentityByAccessTokenFailed(t *testing.T) {
	ctrl := gomock.NewController(nil)
	defer ctrl.Finish()

	mockOauth2Svc := oauth2_svc.NewMockOauth2Service(ctrl)
	controller := provider_ctl.NewProviderContoller(mockOauth2Svc)

	t.Run("EmptyAccessToken", func(t *testing.T) {
		resp, err := controller.GetIdentityByAccessToken(context.TODO(), &provider.GetIdentityByAccessTokenReq{
			RawAccessToken: "",
		})
		assert.Nil(t, err)
		assert.Equal(t, tierrs.ErrCodeBadRequest, resp.BaseResp.ErrCode)
	})

	t.Run("InvalidAccessToken", func(t *testing.T) {
		mockOauth2Svc.EXPECT().GetAccessTokenIdentity(gomock.Any(), gomock.Any()).Return(nil, tierrs.NewNotFound("access_token is expired", nil))
		resp, err := controller.GetIdentityByAccessToken(context.TODO(), &provider.GetIdentityByAccessTokenReq{
			RawAccessToken: "invalid",
		})
		assert.Nil(t, err)
		assert.Equal(t, tierrs.ErrCodeNotFound, resp.BaseResp.ErrCode)
	})
}

func TestGetIdentityByAccessTokenSuccess(t *testing.T) {
	ctrl := gomock.NewController(nil)
	defer ctrl.Finish()

	mockOauth2Svc := oauth2_svc.NewMockOauth2Service(ctrl)
	controller := provider_ctl.NewProviderContoller(mockOauth2Svc)

	now := time.Now()
	ts := utils.ToTimestamp(now)

	cases := []struct {
		name              string
		rawAccessToken    string
		accessTokenEntity entity.Oauth2AccessToken
		expectedIdentity  *provider.AccessTokenIdentity
	}{
		{
			name:           "ValidAccessToken1",
			rawAccessToken: "valid",
			accessTokenEntity: entity.Oauth2AccessToken{
				UserID:               1,
				UserEmail:            "test@example.com",
				OrgID:                2,
				Scopes:               entity.Oauth2Scope{},
				GrantAllProjects:     true,
				ProjectIDs:           entity.ProjectIDs{IDs: []uint64{}},
				AccessTokenExpiresIn: 7200,
				GrantType:            entity.Oauth2DeviceCode,
				Updatable: entity.Updatable{
					Base: entity.Base{
						ID:        1,
						CreatedAt: now,
					},
				},
			},
			expectedIdentity: &provider.AccessTokenIdentity{
				Id:               1,
				UserId:           1,
				UserEmail:        "test@example.com",
				OrgId:            2,
				GrantAllProjects: true,
				ProjectIds:       []uint64{},
				CreatedAt:        ts,
				ExpiresIn:        7200,
				RawAccessToken:   "valid",
				GrantType:        provider.GrantType_DeviceCode,
			},
		},
		{
			name:           "ValidAccessToken2",
			rawAccessToken: "access_token",
			accessTokenEntity: entity.Oauth2AccessToken{
				UserID:               1,
				UserEmail:            "test@example.com",
				OrgID:                2,
				Scopes:               entity.Oauth2Scope{Org: "org:owner", Project: "project:owner"},
				GrantAllProjects:     true,
				ProjectIDs:           entity.ProjectIDs{IDs: []uint64{}},
				AccessTokenExpiresIn: 1,
				GrantType:            entity.Oauth2DeviceCode,
				Updatable: entity.Updatable{
					Base: entity.Base{
						ID:        1,
						CreatedAt: now,
					},
				},
			},
			expectedIdentity: &provider.AccessTokenIdentity{
				Id:               1,
				UserId:           1,
				UserEmail:        "test@example.com",
				OrgId:            2,
				GrantAllProjects: true,
				ProjectIds:       []uint64{},
				CreatedAt:        ts,
				ExpiresIn:        1,
				RawAccessToken:   "access_token",
				GrantType:        provider.GrantType_DeviceCode,
			},
		},
		{
			name:           "ValidAccessToken3",
			rawAccessToken: "access_token",
			accessTokenEntity: entity.Oauth2AccessToken{
				UserID:               100,
				UserEmail:            "test@example.com",
				OrgID:                200,
				Scopes:               entity.Oauth2Scope{Org: "org:owner", Project: "project:owner"},
				GrantAllProjects:     false,
				ProjectIDs:           entity.ProjectIDs{IDs: []uint64{1, 2, 3}},
				AccessTokenExpiresIn: 1,
				GrantType:            entity.Oauth2AuthorizationCode,
				Updatable: entity.Updatable{
					Base: entity.Base{
						ID:        1,
						CreatedAt: now,
					},
				},
			},
			expectedIdentity: &provider.AccessTokenIdentity{
				Id:               1,
				UserId:           100,
				UserEmail:        "test@example.com",
				OrgId:            200,
				GrantAllProjects: false,
				ProjectIds:       []uint64{1, 2, 3},
				CreatedAt:        ts,
				ExpiresIn:        1,
				RawAccessToken:   "access_token",
				GrantType:        provider.GrantType_AuthorizationCode,
			},
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			mockOauth2Svc.EXPECT().GetAccessTokenIdentity(gomock.Any(), c.rawAccessToken).Return(
				&c.accessTokenEntity, nil,
			).Times(1)
			identity, err := controller.GetIdentityByAccessToken(context.TODO(), &provider.GetIdentityByAccessTokenReq{
				RawAccessToken: c.rawAccessToken,
			})
			assert.Nil(t, err)
			assert.Equal(t, c.expectedIdentity, identity.AccessTokenIdentity)
		})
	}
}
