package usecase

import (
	"context"
	"errors"
	"testing"

	"backend/pkg/member/domain/entity"
	"backend/pkg/member/domain/member"
	"backend/pkg/member/domain/usecase"

	"github.com/stretchr/testify/assert"
	"go.uber.org/mock/gomock"
	"google.golang.org/protobuf/proto"

	mockRepo "backend/pkg/member/mock/repository"
)

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

	mockUserRepository := mockRepo.NewMockUserRepository(mockCtrl)

	uc := MustMemberUseCase(MemberUseCaseParam{
		User: mockUserRepository,
	})

	tests := []struct {
		name      string
		req       usecase.CreateUserInfoRequest
		mockSetup func()
		wantErr   bool
	}{
		{
			name: "ok",
			req: usecase.CreateUserInfoRequest{
				UID:               "test-uid",
				AvatarURL:         proto.String("http://example.com/avatar.png"),
				FullName:          proto.String("Test User"),
				Nickname:          proto.String("Tester"),
				GenderCode:        proto.Int64(1),
				UserStatus:        1,
				PreferredLanguage: "en",
				Currency:          "USD",
			},
			mockSetup: func() {
				mockUserRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil)
			},
			wantErr: false,
		},
		{
			name: "failed to bind user info due to insert error",
			req: usecase.CreateUserInfoRequest{
				UID: "test-uid",
			},
			mockSetup: func() {
				mockUserRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(errors.New("database error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup()
			err := uc.BindUserInfo(context.Background(), tt.req)

			if tt.wantErr {
				assert.Error(t, err)
				if err != nil {
					assert.Contains(t, err.Error(), "failed to binding user info")
				}
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

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

	mockAccountRepository := mockRepo.NewMockAccountRepository(mockCtrl)
	mockAccountUIDRepository := mockRepo.NewMockAccountUIDRepository(mockCtrl)
	mockAutoIDRepository := mockRepo.NewMockAutoIDRepository(mockCtrl)

	uc := MustMemberUseCase(MemberUseCaseParam{
		Account:     mockAccountRepository,
		AccountUID:  mockAccountUIDRepository,
		GenerateUID: mockAutoIDRepository,
	})

	tests := []struct {
		name      string
		req       usecase.BindingUser
		mockSetup func()
		wantErr   bool
	}{
		{
			name: "ok",
			req: usecase.BindingUser{
				LoginID: "testLoginID",
				UID:     "testUID",
				Type:    member.AccountTypeMail,
			},
			mockSetup: func() {
				mockAccountRepository.EXPECT().FindOneByAccount(gomock.Any(), "testLoginID").Return(&entity.Account{}, nil)
				mockAccountUIDRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil)
			},
			wantErr: false,
		},
		{
			name: "successful bind with generated UID",
			req: usecase.BindingUser{
				LoginID: "testLoginID",
				Type:    member.AccountTypeMail,
			},
			mockSetup: func() {
				mockAccountRepository.EXPECT().FindOneByAccount(gomock.Any(), "testLoginID").Return(&entity.Account{}, nil)
				//uc.EXPECT().Generate(gomock.Any()).Return("generatedUID", nil)
				mockAutoIDRepository.EXPECT().Inc(gomock.Any(), gomock.Any()).Return(nil)
				mockAutoIDRepository.EXPECT().GetUIDFromNum(gomock.Any()).Return("DOOOOOOD", nil)
				mockAccountUIDRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil)
			},
			wantErr: false,
		},
		{
			name: "failed to find account",
			req: usecase.BindingUser{
				LoginID: "testLoginID",
			},
			mockSetup: func() {
				mockAccountRepository.EXPECT().FindOneByAccount(gomock.Any(), "testLoginID").Return(nil, errors.New("not found"))
			},
			wantErr: true,
		},
		{
			name: "failed to insert account UID",
			req: usecase.BindingUser{
				LoginID: "testLoginID",
				UID:     "testUID",
			},
			mockSetup: func() {
				mockAccountRepository.EXPECT().FindOneByAccount(gomock.Any(), "testLoginID").Return(&entity.Account{}, nil)
				mockAccountUIDRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(errors.New("insert error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup()
			result, err := uc.BindAccount(context.Background(), tt.req)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.req.LoginID, result.LoginID)
				assert.Equal(t, tt.req.Type, result.Type)
			}
		})
	}
}

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

	mockUserRepository := mockRepo.NewMockUserRepository(mockCtrl)

	uc := MustMemberUseCase(MemberUseCaseParam{
		User: mockUserRepository,
	})
	tests := []struct {
		name      string
		uid       string
		email     string
		mockSetup func()
		wantErr   bool
	}{
		{
			name:  "successful email verification",
			uid:   "testUID",
			email: "test@example.com",
			mockSetup: func() {
				mockUserRepository.EXPECT().UpdateEmailVerifyStatus(gomock.Any(), "testUID", "test@example.com").Return(nil)
			},
			wantErr: false,
		},
		{
			name:  "failed email verification",
			uid:   "testUID",
			email: "test@example.com",
			mockSetup: func() {
				mockUserRepository.EXPECT().UpdateEmailVerifyStatus(gomock.Any(), "testUID", "test@example.com").Return(errors.New("update error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup()
			err := uc.BindVerifyEmail(context.Background(), tt.uid, tt.email)

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

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

	mockUserRepository := mockRepo.NewMockUserRepository(mockCtrl)

	uc := MustMemberUseCase(MemberUseCaseParam{
		User: mockUserRepository,
	})

	tests := []struct {
		name      string
		uid       string
		phone     string
		mockSetup func()
		wantErr   bool
	}{
		{
			name:  "successful phone verification",
			uid:   "testUID",
			phone: "1234567890",
			mockSetup: func() {
				mockUserRepository.EXPECT().UpdatePhoneVerifyStatus(gomock.Any(), "testUID", "1234567890").Return(nil)
			},
			wantErr: false,
		},
		{
			name:  "failed phone verification",
			uid:   "testUID",
			phone: "1234567890",
			mockSetup: func() {
				mockUserRepository.EXPECT().UpdatePhoneVerifyStatus(gomock.Any(), "testUID", "1234567890").Return(errors.New("update error"))
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup()
			err := uc.BindVerifyPhone(context.Background(), tt.uid, tt.phone)

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