package usecase

import (
	"backend/pkg/permission/domain"
	"backend/pkg/permission/domain/entity"
	"backend/pkg/permission/domain/usecase"
	mockRepo "backend/pkg/permission/mock/repository"
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/mock/gomock"
)

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

	mockUserRoleRepo := mockRepo.NewMockUserRoleRepository(mockCtrl)
	mockRoleRepo := mockRepo.NewMockRoleRepository(mockCtrl)

	uc := NewUserRoleUseCase(UserRoleUseCaseParam{
		UserRoleRepo: mockUserRoleRepo,
		RoleRepo:     mockRoleRepo,
	})
	ctx := context.Background()

	tests := []struct {
		name      string
		req       usecase.AssignRoleRequest
		mockSetup func()
		wantErr   bool
	}{
		{
			name: "成功指派角色",
			req: usecase.AssignRoleRequest{
				UserUID: "user123",
				RoleUID: "ROLE0000000001",
				Brand:   "brand1",
			},
			mockSetup: func() {
				role := &entity.Role{
					ID:     bson.NewObjectID(),
					UID:    "ROLE0000000001",
					Name:   "管理員",
					Status: domain.RecordActive,
				}
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000001").Return(role, nil)
				mockUserRoleRepo.EXPECT().Exists(ctx, "user123").Return(false, nil)
				mockUserRoleRepo.EXPECT().Create(ctx, gomock.Any()).Return(nil)
			},
			wantErr: false,
		},
		{
			name: "角色不存在",
			req: usecase.AssignRoleRequest{
				UserUID: "user456",
				RoleUID: "ROLE9999999999",
			},
			mockSetup: func() {
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE9999999999").Return(nil, errors.New("not found"))
			},
			wantErr: true,
		},
		{
			name: "角色未啟用",
			req: usecase.AssignRoleRequest{
				UserUID: "user789",
				RoleUID: "ROLE0000000002",
			},
			mockSetup: func() {
				role := &entity.Role{
					ID:     bson.NewObjectID(),
					UID:    "ROLE0000000002",
					Name:   "已停用角色",
					Status: domain.RecordInactive,
				}
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000002").Return(role, nil)
			},
			wantErr: true,
		},
		{
			name: "使用者已有角色",
			req: usecase.AssignRoleRequest{
				UserUID: "user999",
				RoleUID: "ROLE0000000001",
			},
			mockSetup: func() {
				role := &entity.Role{
					ID:     bson.NewObjectID(),
					UID:    "ROLE0000000001",
					Name:   "管理員",
					Status: domain.RecordActive,
				}
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000001").Return(role, nil)
				mockUserRoleRepo.EXPECT().Exists(ctx, "user999").Return(true, nil)
			},
			wantErr: true,
		},
	}

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

			_, err := uc.Assign(ctx, tt.req)

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

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

	mockUserRoleRepo := mockRepo.NewMockUserRoleRepository(mockCtrl)
	mockRoleRepo := mockRepo.NewMockRoleRepository(mockCtrl)

	uc := NewUserRoleUseCase(UserRoleUseCaseParam{
		UserRoleRepo: mockUserRoleRepo,
		RoleRepo:     mockRoleRepo,
	})
	ctx := context.Background()

	tests := []struct {
		name      string
		userUID   string
		newRoleID string
		mockSetup func()
		wantErr   bool
	}{
		{
			name:      "成功更新角色",
			userUID:   "user123",
			newRoleID: "ROLE0000000002",
			mockSetup: func() {
				role := &entity.Role{
					ID:     bson.NewObjectID(),
					UID:    "ROLE0000000002",
					Name:   "新角色",
					Status: domain.RecordActive,
				}
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000002").Return(role, nil)

				updatedUserRole := &entity.UserRole{
					ID:     bson.NewObjectID(),
					Brand:  "brand1",
					UID:    "user123",
					RoleID: "ROLE0000000002",
					Status: domain.RecordActive,
				}
				mockUserRoleRepo.EXPECT().Update(ctx, "user123", "ROLE0000000002").Return(updatedUserRole, nil)
			},
			wantErr: false,
		},
		{
			name:      "新角色不存在",
			userUID:   "user456",
			newRoleID: "ROLE9999999999",
			mockSetup: func() {
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE9999999999").Return(nil, errors.New("not found"))
			},
			wantErr: true,
		},
		{
			name:      "新角色未啟用",
			userUID:   "user789",
			newRoleID: "ROLE0000000003",
			mockSetup: func() {
				role := &entity.Role{
					ID:     bson.NewObjectID(),
					UID:    "ROLE0000000003",
					Name:   "已停用角色",
					Status: domain.RecordInactive,
				}
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000003").Return(role, nil)
			},
			wantErr: true,
		},
	}

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

			result, err := uc.Update(ctx, tt.userUID, tt.newRoleID)

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

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

	mockUserRoleRepo := mockRepo.NewMockUserRoleRepository(mockCtrl)
	mockRoleRepo := mockRepo.NewMockRoleRepository(mockCtrl)

	uc := NewUserRoleUseCase(UserRoleUseCaseParam{
		UserRoleRepo: mockUserRoleRepo,
		RoleRepo:     mockRoleRepo,
	})
	ctx := context.Background()

	tests := []struct {
		name      string
		userUID   string
		mockSetup func()
		wantErr   bool
	}{
		{
			name:    "成功移除角色",
			userUID: "user123",
			mockSetup: func() {
				mockUserRoleRepo.EXPECT().Delete(ctx, "user123").Return(nil)
			},
			wantErr: false,
		},
		{
			name:    "移除失敗",
			userUID: "user456",
			mockSetup: func() {
				mockUserRoleRepo.EXPECT().Delete(ctx, "user456").Return(errors.New("db error"))
			},
			wantErr: true,
		},
	}

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

			err := uc.Remove(ctx, tt.userUID)

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

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

	mockUserRoleRepo := mockRepo.NewMockUserRoleRepository(mockCtrl)
	mockRoleRepo := mockRepo.NewMockRoleRepository(mockCtrl)

	uc := NewUserRoleUseCase(UserRoleUseCaseParam{
		UserRoleRepo: mockUserRoleRepo,
		RoleRepo:     mockRoleRepo,
	})
	ctx := context.Background()

	tests := []struct {
		name      string
		userUID   string
		mockSetup func()
		wantNil   bool
		wantErr   bool
	}{
		{
			name:    "成功獲取使用者角色",
			userUID: "user123",
			mockSetup: func() {
				userRole := &entity.UserRole{
					ID:     bson.NewObjectID(),
					Brand:  "brand1",
					UID:    "user123",
					RoleID: "ROLE0000000001",
					Status: domain.RecordActive,
				}
				mockUserRoleRepo.EXPECT().Get(ctx, "user123").Return(userRole, nil)
			},
			wantNil: false,
			wantErr: false,
		},
		{
			name:    "使用者無角色",
			userUID: "user456",
			mockSetup: func() {
				mockUserRoleRepo.EXPECT().Get(ctx, "user456").Return(nil, errors.New("not found"))
			},
			wantNil: true,
			wantErr: true,
		},
	}

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

			result, err := uc.Get(ctx, tt.userUID)

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

			if tt.wantNil {
				assert.Nil(t, result)
			} else {
				assert.NotNil(t, result)
			}
		})
	}
}

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

	mockUserRoleRepo := mockRepo.NewMockUserRoleRepository(mockCtrl)
	mockRoleRepo := mockRepo.NewMockRoleRepository(mockCtrl)

	uc := NewUserRoleUseCase(UserRoleUseCaseParam{
		UserRoleRepo: mockUserRoleRepo,
		RoleRepo:     mockRoleRepo,
	})
	ctx := context.Background()

	tests := []struct {
		name      string
		roleUID   string
		mockSetup func()
		wantCount int
		wantErr   bool
	}{
		{
			name:    "成功獲取角色的所有使用者",
			roleUID: "ROLE0000000001",
			mockSetup: func() {
				role := &entity.Role{
					ID:     bson.NewObjectID(),
					UID:    "ROLE0000000001",
					Name:   "管理員",
					Status: domain.RecordActive,
				}
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000001").Return(role, nil)

				userRoles := []*entity.UserRole{
					{ID: bson.NewObjectID(), UID: "user1", RoleID: "ROLE0000000001"},
					{ID: bson.NewObjectID(), UID: "user2", RoleID: "ROLE0000000001"},
				}
				mockUserRoleRepo.EXPECT().GetByRoleID(ctx, gomock.Any()).Return(userRoles, nil)
			},
			wantCount: 2,
			wantErr:   false,
		},
		{
			name:    "角色不存在",
			roleUID: "ROLE9999999999",
			mockSetup: func() {
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE9999999999").Return(nil, errors.New("not found"))
			},
			wantCount: 0,
			wantErr:   true,
		},
	}

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

			result, err := uc.GetByRole(ctx, tt.roleUID)

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

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

	mockUserRoleRepo := mockRepo.NewMockUserRoleRepository(mockCtrl)
	mockRoleRepo := mockRepo.NewMockRoleRepository(mockCtrl)

	uc := NewUserRoleUseCase(UserRoleUseCaseParam{
		UserRoleRepo: mockUserRoleRepo,
		RoleRepo:     mockRoleRepo,
	})
	ctx := context.Background()

	tests := []struct {
		name      string
		filter    usecase.UserRoleFilterRequest
		mockSetup func()
		wantCount int
		wantErr   bool
	}{
		{
			name: "成功列出所有使用者角色",
			filter: usecase.UserRoleFilterRequest{
				Brand: "brand1",
			},
			mockSetup: func() {
				userRoles := []*entity.UserRole{
					{ID: bson.NewObjectID(), UID: "user1", RoleID: "ROLE0000000001", Brand: "brand1"},
					{ID: bson.NewObjectID(), UID: "user2", RoleID: "ROLE0000000002", Brand: "brand1"},
				}
				mockUserRoleRepo.EXPECT().List(ctx, gomock.Any()).Return(userRoles, nil)
			},
			wantCount: 2,
			wantErr:   false,
		},
		{
			name: "沒有使用者角色",
			filter: usecase.UserRoleFilterRequest{
				Brand: "unknown",
			},
			mockSetup: func() {
				mockUserRoleRepo.EXPECT().List(ctx, gomock.Any()).Return([]*entity.UserRole{}, nil)
			},
			wantCount: 0,
			wantErr:   false,
		},
		{
			name: "Repository 錯誤",
			filter: usecase.UserRoleFilterRequest{
				Brand: "brand1",
			},
			mockSetup: func() {
				mockUserRoleRepo.EXPECT().List(ctx, gomock.Any()).Return(nil, errors.New("db error"))
			},
			wantCount: 0,
			wantErr:   true,
		},
	}

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

			result, err := uc.List(ctx, tt.filter)

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