package usecase

import (
	errs "backend/pkg/library/errors"
	"backend/pkg/member/domain/config"
	"backend/pkg/member/domain/entity"
	"backend/pkg/member/domain/member"
	"backend/pkg/member/domain/usecase"
	mockRepo "backend/pkg/member/mock/repository"
	"backend/pkg/member/repository"
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/zeromicro/go-zero/core/stores/mon"
	"go.uber.org/mock/gomock"
	"google.golang.org/protobuf/proto"
)

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

	mockAccountRepository := mockRepo.NewMockAccountRepository(mockCtrl)

	uc := MustMemberUseCase(MemberUseCaseParam{
		Account: mockAccountRepository,
		Config: config.Config{
			Bcrypt: struct{ Cost int }{Cost: 10},
		},
	})

	tests := []struct {
		name      string
		req       usecase.CreateLoginUserRequest
		mockSetup func()
		expectErr bool
	}{
		{
			name: "Successful account creation with Digimon platform",
			req: usecase.CreateLoginUserRequest{
				LoginID:  "testuser",
				Token:    "plaintext-password",
				Platform: member.Digimon,
			},
			mockSetup: func() {
				mockAccountRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil)
			},
			expectErr: false,
		},
		{
			name: "Password encryption failure",
			req: usecase.CreateLoginUserRequest{
				LoginID:  "testuser",
				Token:    "plaintext-password",
				Platform: member.Digimon,
			},
			mockSetup: func() {
				HasPasswordFunc = func(password string, cost int) (string, error) {
					return "", errors.New("encryption error")
				}
			},
			expectErr: true,
		},
		{
			name: "Duplicate account insertion error",
			req: usecase.CreateLoginUserRequest{
				LoginID:  "testuser",
				Token:    "plaintext-password",
				Platform: member.Digimon,
			},
			mockSetup: func() {
				HasPasswordFunc = func(password string, cost int) (string, error) {
					return "encrypted-password", nil
				}
				mockAccountRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(errors.New("duplicate account"))
			},
			expectErr: true,
		},
		{
			name: "Successful account creation with non-Digimon platform",
			req: usecase.CreateLoginUserRequest{
				LoginID:  "testuser",
				Platform: member.Google,
			},
			mockSetup: func() {
				mockAccountRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil)
			},
			expectErr: false,
		},
	}

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

			err := uc.CreateUserAccount(context.Background(), tt.req)

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

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

	mockAccountUIDRepo := mockRepo.NewMockAccountUIDRepository(mockCtrl)

	uc := MustMemberUseCase(MemberUseCaseParam{
		AccountUID: mockAccountUIDRepo,
		Config: config.Config{
			Bcrypt: struct{ Cost int }{Cost: 10},
		},
	})

	tests := []struct {
		name      string
		req       usecase.GetUIDByAccountRequest
		mockSetup func()
		wantResp  usecase.GetUIDByAccountResponse
		wantErr   bool
	}{
		{
			name: "Successfully found UID by account",
			req: usecase.GetUIDByAccountRequest{
				Account: "testuser",
			},
			mockSetup: func() {
				mockAccountUIDRepo.EXPECT().
					FindUIDByLoginID(gomock.Any(), "testuser").
					Return(&entity.AccountUID{UID: "12345"}, nil)
			},
			wantResp: usecase.GetUIDByAccountResponse{
				UID:     "12345",
				Account: "testuser",
			},
			wantErr: false,
		},
		{
			name: "Account not found",
			req: usecase.GetUIDByAccountRequest{
				Account: "notfounduser",
			},
			mockSetup: func() {
				mockAccountUIDRepo.EXPECT().
					FindUIDByLoginID(gomock.Any(), "notfounduser").
					Return(nil, errs.ResNotFoundError("account not found"))
			},
			wantResp: usecase.GetUIDByAccountResponse{},
			wantErr:  true,
		},
		{
			name: "Database error",
			req: usecase.GetUIDByAccountRequest{
				Account: "erroruser",
			},
			mockSetup: func() {
				mockAccountUIDRepo.EXPECT().
					FindUIDByLoginID(gomock.Any(), "erroruser").
					Return(nil, errors.New("database error"))
			},
			wantResp: usecase.GetUIDByAccountResponse{},
			wantErr:  true,
		},
	}

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

			resp, err := uc.GetUIDByAccount(context.Background(), tt.req)

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

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

	mockAccountRepo := mockRepo.NewMockAccountRepository(mockCtrl)

	uc := MustMemberUseCase(MemberUseCaseParam{
		Account: mockAccountRepo,
		Config: config.Config{
			Bcrypt: struct{ Cost int }{Cost: 10},
		},
	})

	tests := []struct {
		name      string
		req       usecase.GetUIDByAccountRequest
		mockSetup func()
		expected  usecase.GetAccountInfoResponse
		wantErr   bool
		errCode   string
	}{
		{
			name: "Successfully found account",
			req:  usecase.GetUIDByAccountRequest{Account: "testuser"},
			mockSetup: func() {
				mockAccountRepo.EXPECT().
					FindOneByAccount(gomock.Any(), "testuser").
					Return(&entity.Account{
						LoginID:  "testuser",
						Platform: 1,
						Token:    "testtoken",
					}, nil)
			},
			expected: usecase.GetAccountInfoResponse{
				Data: usecase.CreateLoginUserRequest{
					LoginID:  "testuser",
					Platform: 1,
					Token:    "testtoken",
				},
			},
			wantErr: false,
		},
		{
			name: "Account not found",
			req:  usecase.GetUIDByAccountRequest{Account: "notfounduser"},
			mockSetup: func() {
				mockAccountRepo.EXPECT().
					FindOneByAccount(gomock.Any(), "notfounduser").
					Return(nil, mon.ErrNotFound)
			},
			expected: usecase.GetAccountInfoResponse{},
			wantErr:  true,
		},
		{
			name: "Database error",
			req:  usecase.GetUIDByAccountRequest{Account: "erroruser"},
			mockSetup: func() {
				mockAccountRepo.EXPECT().
					FindOneByAccount(gomock.Any(), "erroruser").
					Return(nil, errors.New("database error"))
			},
			expected: usecase.GetAccountInfoResponse{},
			wantErr:  true,
		},
	}

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

			resp, err := uc.GetUserAccountInfo(context.Background(), tt.req)

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

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

	mockUserRepo := mockRepo.NewMockUserRepository(mockCtrl)

	uc := MustMemberUseCase(MemberUseCaseParam{
		User: mockUserRepo,
		Config: config.Config{
			Bcrypt: struct{ Cost int }{Cost: 10},
		},
	})
	tests := []struct {
		name      string
		req       usecase.GetUserInfoRequest
		mockSetup func()
		expected  usecase.UserInfo
		wantErr   bool
		errCode   string
	}{
		{
			name: "Successfully found user by UID",
			req:  usecase.GetUserInfoRequest{UID: "testUID"},
			mockSetup: func() {
				mockUserRepo.EXPECT().
					FindOneByUID(gomock.Any(), "testUID").
					Return(&entity.User{
						UID:               "testUID",
						Nickname:          proto.String("testNick"),
						Email:             proto.String("test@example.com"),
						PreferredLanguage: "en",
					}, nil)
			},
			expected: usecase.UserInfo{
				CreateUserInfoRequest: usecase.CreateUserInfoRequest{
					UID:               "testUID",
					Nickname:          proto.String("testNick"),
					Email:             proto.String("test@example.com"),
					PreferredLanguage: "en",
				},
			},
			wantErr: false,
		},
		{
			name: "User not found",
			req:  usecase.GetUserInfoRequest{UID: "nonExistentUID"},
			mockSetup: func() {
				mockUserRepo.EXPECT().
					FindOneByUID(gomock.Any(), "nonExistentUID").
					Return(nil, mon.ErrNotFound)
			},
			expected: usecase.UserInfo{},
			wantErr:  true,
		},
		{
			name: "Database error while querying user by UID",
			req:  usecase.GetUserInfoRequest{UID: "errorUID"},
			mockSetup: func() {
				mockUserRepo.EXPECT().
					FindOneByUID(gomock.Any(), "errorUID").
					Return(nil, errors.New("database error"))
			},
			expected: usecase.UserInfo{},
			wantErr:  true,
		},
		{
			name: "Successfully found user by Nickname",
			req:  usecase.GetUserInfoRequest{NickName: "testNick"},
			mockSetup: func() {
				mockUserRepo.EXPECT().
					FindOneByNickName(gomock.Any(), "testNick").
					Return(&entity.User{
						UID:      "testUID",
						Nickname: proto.String("testNick"),
						Email:    proto.String("test@example.com"),
					}, nil)
			},
			expected: usecase.UserInfo{
				CreateUserInfoRequest: usecase.CreateUserInfoRequest{
					UID:      "testUID",
					Nickname: proto.String("testNick"),
					Email:    proto.String("test@example.com"),
				},
			},
			wantErr: false,
		},
		{
			name: "Invalid request, no UID or Nickname",
			req:  usecase.GetUserInfoRequest{},
			mockSetup: func() {
				// No setup needed for invalid request
			},
			expected: usecase.UserInfo{},
			wantErr:  true,
		},
	}

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

			resp, err := uc.GetUserInfo(context.Background(), tt.req)

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

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

	mockAccountRepo := mockRepo.NewMockAccountRepository(mockCtrl)
	uc := MustMemberUseCase(MemberUseCaseParam{
		Account: mockAccountRepo,
	})

	HasPasswordFunc = func(password string, cost int) (string, error) {
		if password == "fail" {
			return "", errors.New("encryption error")
		}
		return "encrypted-password", nil
	}

	tests := []struct {
		name      string
		req       usecase.UpdateTokenRequest
		mockSetup func()
		wantErr   bool
		errCode   string
	}{
		{
			name: "Successful token update",
			req:  usecase.UpdateTokenRequest{Account: "testAccount", Token: "newPassword", Platform: 1},
			mockSetup: func() {
				mockAccountRepo.EXPECT().
					UpdateTokenByLoginID(gomock.Any(), "testAccount", "encrypted-password", gomock.Any()).
					Return(nil)
			},
			wantErr: false,
		},
		{
			name: "Password encryption failure",
			req:  usecase.UpdateTokenRequest{Account: "testAccount", Token: "fail", Platform: 1},
			mockSetup: func() {
				// No repo call expected
			},
			wantErr: true,
		},
		{
			name: "Account not found",
			req:  usecase.UpdateTokenRequest{Account: "nonExistentAccount", Token: "newPassword", Platform: 1},
			mockSetup: func() {
				mockAccountRepo.EXPECT().
					UpdateTokenByLoginID(gomock.Any(), "nonExistentAccount", "encrypted-password", gomock.Any()).
					Return(mon.ErrNotFound)
			},
			wantErr: true,
		},
		{
			name: "Database error during token update",
			req:  usecase.UpdateTokenRequest{Account: "errorAccount", Token: "newPassword", Platform: 1},
			mockSetup: func() {
				mockAccountRepo.EXPECT().
					UpdateTokenByLoginID(gomock.Any(), "errorAccount", "encrypted-password", 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.UpdateUserToken(context.Background(), tt.req)

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

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

	mockUserRepo := mockRepo.NewMockUserRepository(mockCtrl)
	uc := MustMemberUseCase(MemberUseCaseParam{
		User: mockUserRepo,
	})

	tests := []struct {
		name      string
		req       *usecase.UpdateUserInfoRequest
		mockSetup func()
		wantErr   bool
	}{
		{
			name: "Successful update",
			req: &usecase.UpdateUserInfoRequest{
				UID:       "testUID",
				Nickname:  proto.String("UpdatedNick"),
				FullName:  proto.String("Updated Name"),
				AvatarURL: proto.String("http://example.com/avatar.png"),
			},
			mockSetup: func() {
				mockUserRepo.EXPECT().
					UpdateUserDetailsByUID(gomock.Any(), gomock.Any()).
					Return(nil)
			},
			wantErr: false,
		},
		{
			name: "User not found",
			req: &usecase.UpdateUserInfoRequest{
				UID: "nonExistentUID",
			},
			mockSetup: func() {
				mockUserRepo.EXPECT().
					UpdateUserDetailsByUID(gomock.Any(), gomock.Any()).
					Return(repository.ErrNotFound)
			},
			wantErr: true,
		},
		{
			name: "Database error",
			req: &usecase.UpdateUserInfoRequest{
				UID: "errorUID",
			},
			mockSetup: func() {
				mockUserRepo.EXPECT().
					UpdateUserDetailsByUID(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.UpdateUserInfo(context.Background(), tt.req)

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

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

	mockUserRepo := mockRepo.NewMockUserRepository(mockCtrl)
	uc := MustMemberUseCase(MemberUseCaseParam{
		User: mockUserRepo,
	})

	tests := []struct {
		name      string
		req       usecase.UpdateStatusRequest
		mockSetup func()
		wantErr   bool
	}{
		{
			name: "Successful status update",
			req:  usecase.UpdateStatusRequest{UID: "testUID", Status: member.AccountStatusActive},
			mockSetup: func() {
				mockUserRepo.EXPECT().
					UpdateStatus(gomock.Any(), "testUID", gomock.Any()).
					Return(nil)
			},
			wantErr: false,
		},
		{
			name: "User not found",
			req:  usecase.UpdateStatusRequest{UID: "nonExistentUID", Status: member.AccountStatusActive},
			mockSetup: func() {
				mockUserRepo.EXPECT().
					UpdateStatus(gomock.Any(), "nonExistentUID", gomock.Any()).
					Return(repository.ErrNotFound)
			},
			wantErr: true,
		},
		{
			name: "Database error",
			req:  usecase.UpdateStatusRequest{UID: "errorUID", Status: member.AccountStatusUninitialized},
			mockSetup: func() {
				mockUserRepo.EXPECT().
					UpdateStatus(gomock.Any(), "errorUID", 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.UpdateStatus(context.Background(), tt.req)

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

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

	mockUserRepo := mockRepo.NewMockUserRepository(mockCtrl)
	uc := MustMemberUseCase(MemberUseCaseParam{
		User: mockUserRepo,
	})

	tests := []struct {
		name      string
		req       usecase.ListUserInfoRequest
		mockSetup func()
		wantErr   bool
	}{
		{
			name: "Successful member listing",
			req: usecase.ListUserInfoRequest{
				PageSize:  10,
				PageIndex: 1,
			},
			mockSetup: func() {
				mockUserRepo.EXPECT().
					ListMembers(gomock.Any(), gomock.Any()).
					Return([]*entity.User{
						{UID: "testUID1"},
						{UID: "testUID2"},
					}, int64(2), nil)
			},
			wantErr: false,
		},
		{
			name: "Database error",
			req: usecase.ListUserInfoRequest{
				PageSize:  10,
				PageIndex: 1,
			},
			mockSetup: func() {
				mockUserRepo.EXPECT().
					ListMembers(gomock.Any(), gomock.Any()).
					Return(nil, int64(0), errors.New("database error"))
			},
			wantErr: true,
		},
	}

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

			resp, err := uc.ListMember(context.Background(), tt.req)

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