package repository

import (
	"backend/pkg/permission/domain"
	"backend/pkg/permission/domain/entity"
	domainRepo "backend/pkg/permission/domain/repository"
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/alicebob/miniredis/v2"
	"github.com/zeromicro/go-zero/core/stores/redis"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/zeromicro/go-zero/core/stores/cache"
	"go.mongodb.org/mongo-driver/v2/bson"

	mgo "backend/pkg/library/mongo"
)

func setupRoleRepo(db string) (domainRepo.RoleRepository, func(), error) {
	h, p, tearDown, err := startMongoContainer()
	if err != nil {
		return nil, nil, err
	}
	s, _ := miniredis.Run()

	conf := &mgo.Conf{
		Schema:                           mongoSchema,
		Host:                             fmt.Sprintf("%s:%s", h, p),
		Database:                         db,
		MaxStaleness:                     300,
		MaxPoolSize:                      100,
		MinPoolSize:                      100,
		MaxConnIdleTime:                  300,
		Compressors:                      []string{},
		EnableStandardReadWriteSplitMode: false,
		ConnectTimeoutMs:                 3000,
	}

	cacheConf := cache.CacheConf{
		cache.NodeConf{
			RedisConf: redis.RedisConf{
				Host: s.Addr(),
				Type: redis.NodeType,
			},
			Weight: 100,
		},
	}

	cacheOpts := []cache.Option{
		cache.WithExpiry(1000 * time.Microsecond),
		cache.WithNotFoundExpiry(1000 * time.Microsecond),
	}

	param := RoleRepositoryParam{
		Conf:      conf,
		CacheConf: cacheConf,
		CacheOpts: cacheOpts,
	}
	repo := NewRoleRepository(param)
	_, _ = repo.Index20251009002UP(context.Background())

	return repo, tearDown, nil
}

func TestRoleRepository_CreateAndGet(t *testing.T) {
	repo, tearDown, err := setupRoleRepo("testDB")
	defer tearDown()
	assert.NoError(t, err)

	ctx := context.Background()

	tests := []struct {
		name    string
		role    *entity.Role
		wantErr bool
	}{
		{
			name: "成功創建角色",
			role: &entity.Role{
				ID:       bson.NewObjectID(),
				UID:      "ROLE0000000001",
				ClientID: 1,
				Name:     "管理員",
				Status:   domain.RecordActive,
			},
			wantErr: false,
		},
		{
			name: "創建另一個角色",
			role: &entity.Role{
				ID:       bson.NewObjectID(),
				UID:      "ROLE0000000002",
				ClientID: 1,
				Name:     "一般使用者",
				Status:   domain.RecordActive,
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := repo.Create(ctx, tt.role)

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

				// 驗證可以找到創建的角色
				retrieved, err := repo.GetByUID(ctx, tt.role.UID)
				assert.NoError(t, err)
				assert.Equal(t, tt.role.UID, retrieved.UID)
				assert.Equal(t, tt.role.Name, retrieved.Name)
				assert.Equal(t, tt.role.ClientID, retrieved.ClientID)
			}
		})
	}
}

func TestRoleRepository_GetByUID(t *testing.T) {
	repo, tearDown, err := setupRoleRepo("testDB")
	defer tearDown()
	assert.NoError(t, err)
	ctx := context.Background()

	// 準備測試數據
	testRole := &entity.Role{
		ID:       bson.NewObjectID(),
		UID:      "ROLE0000000001",
		ClientID: 1,
		Name:     "測試角色",
		Status:   domain.RecordActive,
	}
	err = repo.Create(ctx, testRole)
	require.NoError(t, err)

	tests := []struct {
		name    string
		uid     string
		wantErr error
		check   func(*testing.T, *entity.Role)
	}{
		{
			name:    "找到存在的角色",
			uid:     "ROLE0000000001",
			wantErr: nil,
			check: func(t *testing.T, role *entity.Role) {
				assert.Equal(t, "測試角色", role.Name)
				assert.Equal(t, 1, role.ClientID)
			},
		},
		{
			name:    "不存在的角色",
			uid:     "ROLE9999999999",
			wantErr: ErrNotFound,
			check:   nil,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			role, err := repo.GetByUID(ctx, tt.uid)

			if tt.wantErr != nil {
				assert.ErrorIs(t, err, tt.wantErr)
				assert.Nil(t, role)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, role)
				if tt.check != nil {
					tt.check(t, role)
				}
			}
		})
	}
}

func TestRoleRepository_Update(t *testing.T) {
	repo, tearDown, err := setupRoleRepo("testDB")
	defer tearDown()
	assert.NoError(t, err)

	ctx := context.Background()

	// 準備測試數據
	testRole := &entity.Role{
		ID:       bson.NewObjectID(),
		UID:      "ROLE0000000001",
		ClientID: 1,
		Name:     "原始名稱",
		Status:   domain.RecordActive,
	}
	err = repo.Create(ctx, testRole)
	require.NoError(t, err)

	tests := []struct {
		name    string
		update  func(*entity.Role)
		wantErr bool
		check   func(*testing.T, *entity.Role)
	}{
		{
			name: "更新角色名稱",
			update: func(role *entity.Role) {
				role.Name = "新的名稱"
			},
			wantErr: false,
			check: func(t *testing.T, role *entity.Role) {
				assert.Equal(t, "新的名稱", role.Name)
			},
		},
		{
			name: "更新角色狀態",
			update: func(role *entity.Role) {
				role.Status = domain.RecordInactive
			},
			wantErr: false,
			check: func(t *testing.T, role *entity.Role) {
				assert.Equal(t, domain.RecordInactive, role.Status)
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// 獲取當前角色
			role, err := repo.GetByUID(ctx, testRole.UID)
			require.NoError(t, err)

			// 應用更新
			tt.update(role)

			// 執行更新
			err = repo.Update(ctx, role)

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

				// 驗證更新
				updated, err := repo.GetByUID(ctx, testRole.UID)
				assert.NoError(t, err)
				if tt.check != nil {
					tt.check(t, updated)
				}
			}
		})
	}
}

func TestRoleRepository_Delete(t *testing.T) {
	repo, tearDown, err := setupRoleRepo("testDB")
	defer tearDown()
	assert.NoError(t, err)

	ctx := context.Background()

	// 準備測試數據
	testRole := &entity.Role{
		ID:       bson.NewObjectID(),
		UID:      "ROLE0000000001",
		ClientID: 1,
		Name:     "要刪除的角色",
		Status:   domain.RecordActive,
	}
	err = repo.Create(ctx, testRole)
	require.NoError(t, err)

	t.Run("軟刪除角色", func(t *testing.T) {
		err := repo.Delete(ctx, testRole.UID)
		assert.NoError(t, err)

		// 驗證角色狀態變為已刪除
		role, err := repo.GetByUID(ctx, testRole.UID)
		assert.NoError(t, err)
		assert.Equal(t, domain.RecordDeleted, role.Status)
	})
}

func TestRoleRepository_List(t *testing.T) {
	repo, tearDown, err := setupRoleRepo("testDB")
	defer tearDown()
	assert.NoError(t, err)

	ctx := context.Background()

	// 準備測試數據
	testRoles := []*entity.Role{
		{
			ID:       bson.NewObjectID(),
			UID:      "ROLE0000000001",
			ClientID: 1,
			Name:     "管理員",
			Status:   domain.RecordActive,
		},
		{
			ID:       bson.NewObjectID(),
			UID:      "ROLE0000000002",
			ClientID: 1,
			Name:     "編輯者",
			Status:   domain.RecordActive,
		},
		{
			ID:       bson.NewObjectID(),
			UID:      "ROLE0000000003",
			ClientID: 2,
			Name:     "訪客",
			Status:   domain.RecordInactive,
		},
	}

	for _, role := range testRoles {
		err := repo.Create(ctx, role)
		require.NoError(t, err)
	}

	tests := []struct {
		name      string
		filter    domainRepo.RoleFilter
		wantCount int
	}{
		{
			name:      "列出所有角色",
			filter:    domainRepo.RoleFilter{},
			wantCount: 3,
		},
		{
			name: "按 ClientID 過濾",
			filter: domainRepo.RoleFilter{
				ClientID: 1,
			},
			wantCount: 2,
		},
		{
			name: "按名稱模糊搜尋",
			filter: domainRepo.RoleFilter{
				Name: "管理",
			},
			wantCount: 1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			roles, err := repo.List(ctx, tt.filter)

			assert.NoError(t, err)
			assert.Len(t, roles, tt.wantCount)
		})
	}
}

func TestRoleRepository_Page(t *testing.T) {
	repo, tearDown, err := setupRoleRepo("testDB")
	defer tearDown()
	assert.NoError(t, err)

	ctx := context.Background()

	// 準備測試數據 - 創建 15 個角色
	for i := 1; i <= 15; i++ {
		role := &entity.Role{
			ID:       bson.NewObjectID(),
			UID:      bson.NewObjectID().Hex(),
			ClientID: 1,
			Name:     bson.NewObjectID().Hex(),
			Status:   domain.RecordActive,
		}
		err := repo.Create(ctx, role)
		require.NoError(t, err)
	}

	tests := []struct {
		name      string
		page      int
		size      int
		wantCount int
		wantTotal int64
	}{
		{
			name:      "第一頁，每頁 10 筆",
			page:      1,
			size:      10,
			wantCount: 10,
			wantTotal: 15,
		},
		{
			name:      "第二頁，每頁 10 筆",
			page:      2,
			size:      10,
			wantCount: 5,
			wantTotal: 15,
		},
		{
			name:      "第一頁，每頁 5 筆",
			page:      1,
			size:      5,
			wantCount: 5,
			wantTotal: 15,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			roles, total, err := repo.Page(ctx, domainRepo.RoleFilter{}, tt.page, tt.size)

			assert.NoError(t, err)
			assert.Len(t, roles, tt.wantCount)
			assert.Equal(t, tt.wantTotal, total)
		})
	}
}

func TestRoleRepository_Exists(t *testing.T) {
	repo, tearDown, err := setupRoleRepo("testDB")
	defer tearDown()
	assert.NoError(t, err)

	ctx := context.Background()

	// 準備測試數據
	testRole := &entity.Role{
		ID:       bson.NewObjectID(),
		UID:      "ROLE0000000001",
		ClientID: 1,
		Name:     "測試角色",
		Status:   domain.RecordActive,
	}
	err = repo.Create(ctx, testRole)
	require.NoError(t, err)

	tests := []struct {
		name   string
		uid    string
		exists bool
	}{
		{
			name:   "存在的角色",
			uid:    "ROLE0000000001",
			exists: true,
		},
		{
			name:   "不存在的角色",
			uid:    "ROLE9999999999",
			exists: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			exists, err := repo.Exists(ctx, tt.uid)

			assert.NoError(t, err)
			assert.Equal(t, tt.exists, exists)
		})
	}
}
