package usecase

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

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

func stringPtr(s string) *string {
	return &s
}

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

	mockRoleRepo := mockRepo.NewMockRoleRepository(mockCtrl)
	mockUserRoleRepo := mockRepo.NewMockUserRoleRepository(mockCtrl)
	mockRolePermUC := mockUC.NewMockRolePermissionUseCase(mockCtrl)

	uc := NewRoleUseCase(RoleUseCaseParam{
		RoleRepo:        mockRoleRepo,
		UserRoleRepo:    mockUserRoleRepo,
		RolePermUseCase: mockRolePermUC,
	})
	ctx := context.Background()

	tests := []struct {
		name      string
		req       usecase.CreateRoleRequest
		mockSetup func()
		wantErr   bool
	}{
		{
			name: "成功創建角色",
			req: usecase.CreateRoleRequest{
				ClientID: 1,
				Name:     "管理員",
			},
			mockSetup: func() {
				mockRoleRepo.EXPECT().NextID(ctx).Return(int64(1), nil)
				mockRoleRepo.EXPECT().Create(ctx, gomock.Any()).Return(nil)

				role := &entity.Role{
					ID:       bson.NewObjectID(),
					UID:      "ROLE0000000001",
					ClientID: 1,
					Name:     "管理員",
					Status:   domain.RecordActive,
				}
				mockRoleRepo.EXPECT().GetByUID(ctx, gomock.Any()).Return(role, nil)
				mockRolePermUC.EXPECT().GetByRoleUID(ctx, gomock.Any()).Return(nil, nil)
			},
			wantErr: false,
		},
		{
			name: "NextID 失敗",
			req: usecase.CreateRoleRequest{
				ClientID: 1,
				Name:     "管理員",
			},
			mockSetup: func() {
				mockRoleRepo.EXPECT().NextID(ctx).Return(int64(0), errors.New("db error"))
			},
			wantErr: true,
		},
		{
			name: "Create 失敗",
			req: usecase.CreateRoleRequest{
				ClientID: 1,
				Name:     "管理員",
			},
			mockSetup: func() {
				mockRoleRepo.EXPECT().NextID(ctx).Return(int64(1), nil)
				mockRoleRepo.EXPECT().Create(ctx, gomock.Any()).Return(errors.New("db error"))
			},
			wantErr: true,
		},
	}

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

			result, err := uc.Create(ctx, tt.req)

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

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

	mockRoleRepo := mockRepo.NewMockRoleRepository(mockCtrl)
	mockUserRoleRepo := mockRepo.NewMockUserRoleRepository(mockCtrl)
	mockRolePermUC := mockUC.NewMockRolePermissionUseCase(mockCtrl)

	uc := NewRoleUseCase(RoleUseCaseParam{
		RoleRepo:        mockRoleRepo,
		UserRoleRepo:    mockUserRoleRepo,
		RolePermUseCase: mockRolePermUC,
	})
	ctx := context.Background()

	tests := []struct {
		name      string
		uid       string
		req       usecase.UpdateRoleRequest
		mockSetup func()
		wantErr   bool
	}{
		{
			name: "成功更新角色",
			uid:  "ROLE0000000001",
			req: usecase.UpdateRoleRequest{
				Name: stringPtr("新管理員"),
			},
			mockSetup: func() {
				role := &entity.Role{
					ID:       bson.NewObjectID(),
					UID:      "ROLE0000000001",
					ClientID: 1,
					Name:     "管理員",
					Status:   domain.RecordActive,
				}
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000001").Return(role, nil)
				mockRoleRepo.EXPECT().Update(ctx, gomock.Any()).Return(nil)
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000001").Return(role, nil)
				mockRolePermUC.EXPECT().GetByRoleUID(ctx, "ROLE0000000001").Return(nil, nil)
			},
			wantErr: false,
		},
		{
			name: "角色不存在",
			uid:  "ROLE9999999999",
			req: usecase.UpdateRoleRequest{
				Name: stringPtr("新管理員"),
			},
			mockSetup: func() {
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE9999999999").Return(nil, errors.New("not found"))
			},
			wantErr: true,
		},
	}

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

			result, err := uc.Update(ctx, tt.uid, tt.req)

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

func TestRoleUseCase_Delete(t *testing.T) {
	ctx := context.Background()

	tests := []struct {
		name      string
		uid       string
		mockSetup func(*mockRepo.MockRoleRepository, *mockRepo.MockUserRoleRepository)
		wantErr   bool
	}{
		{
			name: "成功刪除角色",
			uid:  "ROLE0000000001",
			mockSetup: func(mockRoleRepo *mockRepo.MockRoleRepository, mockUserRoleRepo *mockRepo.MockUserRoleRepository) {
				role := &entity.Role{
					ID:       bson.NewObjectID(),
					UID:      "ROLE0000000001",
					ClientID: 1,
					Name:     "管理員",
					Status:   domain.RecordActive,
				}
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000001").Return(role, nil)
				mockUserRoleRepo.EXPECT().GetByRoleID(ctx, "ROLE0000000001").Return([]*entity.UserRole{}, nil)
				mockRoleRepo.EXPECT().Delete(ctx, "ROLE0000000001").Return(nil)
			},
			wantErr: false,
		},
		{
			name: "角色不存在",
			uid:  "ROLE9999999999",
			mockSetup: func(mockRoleRepo *mockRepo.MockRoleRepository, mockUserRoleRepo *mockRepo.MockUserRoleRepository) {
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE9999999999").Return(nil, errors.New("not found"))
			},
			wantErr: true,
		},
		{
			name: "角色正在使用中",
			uid:  "ROLE0000000001",
			mockSetup: func(mockRoleRepo *mockRepo.MockRoleRepository, mockUserRoleRepo *mockRepo.MockUserRoleRepository) {
				role := &entity.Role{
					ID:       bson.NewObjectID(),
					UID:      "ROLE0000000001",
					ClientID: 1,
					Name:     "管理員",
					Status:   domain.RecordActive,
				}
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000001").Return(role, nil)
				mockUserRoleRepo.EXPECT().GetByRoleID(ctx, "ROLE0000000001").Return([]*entity.UserRole{
					{UID: "user1"},
					{UID: "user2"},
				}, nil)
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// 為每個測試案例創建新的 mock controller 和 usecase 實例
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockRoleRepo := mockRepo.NewMockRoleRepository(mockCtrl)
			mockUserRoleRepo := mockRepo.NewMockUserRoleRepository(mockCtrl)
			mockRolePermUC := mockUC.NewMockRolePermissionUseCase(mockCtrl)

			tt.mockSetup(mockRoleRepo, mockUserRoleRepo)

			uc := NewRoleUseCase(RoleUseCaseParam{
				RoleRepo:        mockRoleRepo,
				UserRoleRepo:    mockUserRoleRepo,
				RolePermUseCase: mockRolePermUC,
			})

			err := uc.Delete(ctx, tt.uid)

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

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

	mockRoleRepo := mockRepo.NewMockRoleRepository(mockCtrl)
	mockUserRoleRepo := mockRepo.NewMockUserRoleRepository(mockCtrl)
	mockRolePermUC := mockUC.NewMockRolePermissionUseCase(mockCtrl)

	uc := NewRoleUseCase(RoleUseCaseParam{
		RoleRepo:        mockRoleRepo,
		UserRoleRepo:    mockUserRoleRepo,
		RolePermUseCase: mockRolePermUC,
	})
	ctx := context.Background()

	tests := []struct {
		name      string
		uid       string
		mockSetup func()
		wantNil   bool
		wantErr   bool
	}{
		{
			name: "成功獲取角色",
			uid:  "ROLE0000000001",
			mockSetup: func() {
				role := &entity.Role{
					ID:       bson.NewObjectID(),
					UID:      "ROLE0000000001",
					ClientID: 1,
					Name:     "管理員",
					Status:   domain.RecordActive,
				}
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE0000000001").Return(role, nil)
				mockRolePermUC.EXPECT().GetByRoleUID(ctx, "ROLE0000000001").Return(nil, nil)
			},
			wantNil: false,
			wantErr: false,
		},
		{
			name: "角色不存在",
			uid:  "ROLE9999999999",
			mockSetup: func() {
				mockRoleRepo.EXPECT().GetByUID(ctx, "ROLE9999999999").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.uid)

			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 TestRoleUseCase_List(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mockRoleRepo := mockRepo.NewMockRoleRepository(mockCtrl)
	mockUserRoleRepo := mockRepo.NewMockUserRoleRepository(mockCtrl)
	mockRolePermUC := mockUC.NewMockRolePermissionUseCase(mockCtrl)

	uc := NewRoleUseCase(RoleUseCaseParam{
		RoleRepo:        mockRoleRepo,
		UserRoleRepo:    mockUserRoleRepo,
		RolePermUseCase: mockRolePermUC,
	})
	ctx := context.Background()

	tests := []struct {
		name      string
		filter    usecase.RoleFilterRequest
		mockSetup func()
		wantCount int
		wantErr   bool
	}{
		{
			name: "成功列出所有角色",
			filter: usecase.RoleFilterRequest{
				ClientID: 1,
			},
			mockSetup: func() {
				roles := []*entity.Role{
					{ID: bson.NewObjectID(), UID: "ROLE0000000001", Name: "管理員", Status: domain.RecordActive},
					{ID: bson.NewObjectID(), UID: "ROLE0000000002", Name: "用戶", Status: domain.RecordActive},
				}
				mockRoleRepo.EXPECT().List(ctx, gomock.Any()).Return(roles, nil)
				mockRolePermUC.EXPECT().GetByRoleUID(ctx, gomock.Any()).Return(nil, nil).Times(2)
			},
			wantCount: 2,
			wantErr:   false,
		},
		{
			name: "沒有角色",
			filter: usecase.RoleFilterRequest{
				ClientID: 999,
			},
			mockSetup: func() {
				mockRoleRepo.EXPECT().List(ctx, gomock.Any()).Return([]*entity.Role{}, nil)
			},
			wantCount: 0,
			wantErr:   false,
		},
		{
			name: "Repository 錯誤",
			filter: usecase.RoleFilterRequest{
				ClientID: 1,
			},
			mockSetup: func() {
				mockRoleRepo.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)
			}
		})
	}
}
