package usecase

import (
	errs "backend/pkg/library/errors"
	"context"
	"errors"
	"testing"

	"backend/pkg/member/domain/entity"
	"backend/pkg/member/domain/member"
	"backend/pkg/member/domain/usecase"
	mockRepo "backend/pkg/member/mock/repository"

	"github.com/stretchr/testify/assert"
	"go.uber.org/mock/gomock"
)

func TestGenerateRefreshCode(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mockVerifyCodeModel := mockRepo.NewMockVerifyCodeRepository(mockCtrl)
	uc := MustMemberUseCase(MemberUseCaseParam{
		VerifyCodeModel: mockVerifyCodeModel,
	})

	tests := []struct {
		name      string
		param     usecase.GenerateRefreshCodeRequest
		mockSetup func()
		wantErr   bool
	}{
		{
			name: "Successful code generation",
			param: usecase.GenerateRefreshCodeRequest{
				LoginID:  "testLoginID",
				CodeType: member.GenerateCodeTypeEmail,
			},
			mockSetup: func() {
				mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("", nil)
				mockVerifyCodeModel.EXPECT().SetVerifyCode(gomock.Any(), "testLoginID", "email", gomock.Any()).Return(nil)
			},
			wantErr: false,
		},
		{
			name: "Code type not found",
			param: usecase.GenerateRefreshCodeRequest{
				LoginID:  "testLoginID",
				CodeType: -999, // Invalid code type
			},
			mockSetup: func() {},
			wantErr:   true,
		},
		{
			name: "Existing code retrieval",
			param: usecase.GenerateRefreshCodeRequest{
				LoginID:  "testLoginID",
				CodeType: member.GenerateCodeTypeEmail,
			},
			mockSetup: func() {
				mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("123456", nil)
			},
			wantErr: false,
		},
		{
			name: "Set verify code failure",
			param: usecase.GenerateRefreshCodeRequest{
				LoginID:  "testLoginID",
				CodeType: member.GenerateCodeTypeEmail,
			},
			mockSetup: func() {
				mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("", nil)
				mockVerifyCodeModel.EXPECT().SetVerifyCode(gomock.Any(), "testLoginID", "email", gomock.Any()).Return(errors.New("redis error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup()

			resp, err := uc.GenerateRefreshCode(context.Background(), tt.param)

			if tt.wantErr {
				assert.Error(t, err)
				assert.Empty(t, resp)
			} else {
				assert.NoError(t, err)
				assert.NotEmpty(t, resp.Data.VerifyCode)
			}
		})
	}
}

func TestCheckRefreshCode(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mockVerifyCodeModel := mockRepo.NewMockVerifyCodeRepository(mockCtrl)
	uc := MustMemberUseCase(MemberUseCaseParam{
		VerifyCodeModel: mockVerifyCodeModel,
	})

	tests := []struct {
		name      string
		param     usecase.VerifyRefreshCodeRequest
		mockSetup func()
		wantErr   bool
	}{
		{
			name: "Successful verification",
			param: usecase.VerifyRefreshCodeRequest{
				LoginID:    "testLoginID",
				CodeType:   member.GenerateCodeTypeEmail,
				VerifyCode: "123456",
			},
			mockSetup: func() {
				mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("123456", nil)
			},
			wantErr: false,
		},
		{
			name: "Code type not found",
			param: usecase.VerifyRefreshCodeRequest{
				LoginID:    "testLoginID",
				CodeType:   -1, // Invalid CodeType
				VerifyCode: "123456",
			},
			mockSetup: func() {},
			wantErr:   true,
		},
		{
			name: "Code not found in Redis",
			param: usecase.VerifyRefreshCodeRequest{
				LoginID:    "testLoginID",
				CodeType:   member.GenerateCodeTypeEmail,
				VerifyCode: "123456",
			},
			mockSetup: func() {
				mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("", nil)
			},
			wantErr: true,
		},
		{
			name: "Verification code mismatch",
			param: usecase.VerifyRefreshCodeRequest{
				LoginID:    "testLoginID",
				CodeType:   member.GenerateCodeTypeEmail,
				VerifyCode: "654321", // Mismatch
			},
			mockSetup: func() {
				mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("123456", nil)
			},
			wantErr: true,
		},
		{
			name: "Redis retrieval error",
			param: usecase.VerifyRefreshCodeRequest{
				LoginID:    "testLoginID",
				CodeType:   member.GenerateCodeTypeEmail,
				VerifyCode: "123456",
			},
			mockSetup: func() {
				mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("", errors.New("redis error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup()

			err := uc.CheckRefreshCode(context.Background(), tt.param)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestVerifyPlatformAuthResult(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mockAccountRepo := mockRepo.NewMockAccountRepository(mockCtrl)
	uc := MustMemberUseCase(MemberUseCaseParam{
		Account: mockAccountRepo,
	})
	token, err := HashPassword("password", 10)
	assert.NoError(t, err)

	tests := []struct {
		name      string
		param     usecase.VerifyAuthResultRequest
		mockSetup func()
		wantResp  usecase.VerifyAuthResultResponse
		wantErr   bool
	}{
		{
			name: "Successful verification",
			param: usecase.VerifyAuthResultRequest{
				Account: "testAccount",
				Token:   "password",
			},
			mockSetup: func() {
				mockAccountRepo.EXPECT().FindOneByAccount(gomock.Any(), "testAccount").Return(&entity.Account{
					Token: token,
				}, nil)
			},
			wantResp: usecase.VerifyAuthResultResponse{
				Status: true,
			},
			wantErr: false,
		},
		{
			name: "Invalid token verification",
			param: usecase.VerifyAuthResultRequest{
				Account: "testAccount",
				Token:   "invalidToken",
			},
			mockSetup: func() {
				mockAccountRepo.EXPECT().FindOneByAccount(gomock.Any(), "testAccount").Return(&entity.Account{
					Token: "validToken",
				}, nil)
			},
			wantResp: usecase.VerifyAuthResultResponse{
				Status: false,
			},
			wantErr: false,
		},
		{
			name: "Account not found",
			param: usecase.VerifyAuthResultRequest{
				Account: "nonExistentAccount",
				Token:   "someToken",
			},
			mockSetup: func() {
				mockAccountRepo.EXPECT().FindOneByAccount(gomock.Any(), "nonExistentAccount").Return(nil, errs.ResNotFoundError("account not found"))
			},
			wantResp: usecase.VerifyAuthResultResponse{},
			wantErr:  true,
		},
		{
			name: "Database error",
			param: usecase.VerifyAuthResultRequest{
				Account: "testAccount",
				Token:   "someToken",
			},
			mockSetup: func() {
				mockAccountRepo.EXPECT().FindOneByAccount(gomock.Any(), "testAccount").Return(nil, errors.New("database error"))
			},
			wantResp: usecase.VerifyAuthResultResponse{},
			wantErr:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup()

			resp, err := uc.VerifyPlatformAuthResult(context.Background(), tt.param)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.wantResp, resp)
			}
		})
	}
}
