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 setupUserRoleRepo(db string) (domainRepo.UserRoleRepository, 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 := UserRoleRepositoryParam{
		Conf:      conf,
		CacheConf: cacheConf,
		CacheOpts: cacheOpts,
	}
	repo := NewUserRoleRepository(param)
	_, _ = repo.Index20251009004UP(context.Background())

	return repo, tearDown, nil
}

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

	ctx := context.Background()

	tests := []struct {
		name     string
		userRole *entity.UserRole
		wantErr  bool
	}{
		{
			name: "成功創建使用者角色",
			userRole: &entity.UserRole{
				ID:     bson.NewObjectID(),
				Brand:  "brand1",
				UID:    "user123",
				RoleID: "ROLE0000000001",
				Status: domain.RecordActive,
			},
			wantErr: false,
		},
		{
			name: "創建另一個使用者角色",
			userRole: &entity.UserRole{
				ID:     bson.NewObjectID(),
				Brand:  "brand2",
				UID:    "user456",
				RoleID: "ROLE0000000002",
				Status: domain.RecordActive,
			},
			wantErr: false,
		},
	}

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

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

				// 驗證可以找到創建的使用者角色
				retrieved, err := repo.Get(ctx, tt.userRole.UID)
				assert.NoError(t, err)
				assert.Equal(t, tt.userRole.UID, retrieved.UID)
				assert.Equal(t, tt.userRole.RoleID, retrieved.RoleID)
				assert.Equal(t, tt.userRole.Brand, retrieved.Brand)
			}
		})
	}
}

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

	ctx := context.Background()

	// 準備測試數據
	testUserRole := &entity.UserRole{
		ID:     bson.NewObjectID(),
		Brand:  "brand1",
		UID:    "user123",
		RoleID: "ROLE0000000001",
		Status: domain.RecordActive,
	}
	err = repo.Create(ctx, testUserRole)
	require.NoError(t, err)

	tests := []struct {
		name    string
		uid     string
		wantErr error
		check   func(*testing.T, *entity.UserRole)
	}{
		{
			name:    "找到存在的使用者角色",
			uid:     "user123",
			wantErr: nil,
			check: func(t *testing.T, ur *entity.UserRole) {
				assert.Equal(t, "ROLE0000000001", ur.RoleID)
				assert.Equal(t, "brand1", ur.Brand)
			},
		},
		{
			name:    "不存在的使用者",
			uid:     "user999",
			wantErr: ErrNotFound,
			check:   nil,
		},
	}

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

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

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

	// 準備測試數據
	testUserRole := &entity.UserRole{
		ID:     bson.NewObjectID(),
		Brand:  "brand1",
		UID:    "user123",
		RoleID: "ROLE0000000001",
		Status: domain.RecordActive,
	}
	err = repo.Create(ctx, testUserRole)
	require.NoError(t, err)

	tests := []struct {
		name      string
		uid       string
		newRoleID string
		wantErr   error
		check     func(*testing.T, *entity.UserRole)
	}{
		{
			name:      "成功更新角色",
			uid:       "user123",
			newRoleID: "ROLE0000000002",
			wantErr:   nil,
			check: func(t *testing.T, ur *entity.UserRole) {
				assert.Equal(t, "ROLE0000000002", ur.RoleID)
			},
		},
		{
			name:      "更新不存在的使用者",
			uid:       "user999",
			newRoleID: "ROLE0000000003",
			wantErr:   ErrNotFound,
			check:     nil,
		},
	}

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

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

				// 驗證更新
				retrieved, err := repo.Get(ctx, tt.uid)
				assert.NoError(t, err)
				assert.Equal(t, tt.newRoleID, retrieved.RoleID)
			}
		})
	}
}

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

	ctx := context.Background()

	// 準備測試數據
	testUserRole := &entity.UserRole{
		ID:     bson.NewObjectID(),
		Brand:  "brand1",
		UID:    "user123",
		RoleID: "ROLE0000000001",
		Status: domain.RecordActive,
	}
	err = repo.Create(ctx, testUserRole)
	require.NoError(t, err)

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

		// 驗證已被刪除
		_, err = repo.Get(ctx, testUserRole.UID)
		assert.ErrorIs(t, err, ErrNotFound)
	})

	t.Run("刪除不存在的使用者", func(t *testing.T) {
		err := repo.Delete(ctx, "user999")
		assert.NoError(t, err) // 刪除不存在的記錄不應該報錯
	})
}

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

	ctx := context.Background()

	// 準備測試數據
	testUserRoles := []*entity.UserRole{
		{
			ID:     bson.NewObjectID(),
			Brand:  "brand1",
			UID:    "user1",
			RoleID: "ROLE0000000001",
			Status: domain.RecordActive,
		},
		{
			ID:     bson.NewObjectID(),
			Brand:  "brand1",
			UID:    "user2",
			RoleID: "ROLE0000000001",
			Status: domain.RecordActive,
		},
		{
			ID:     bson.NewObjectID(),
			Brand:  "brand2",
			UID:    "user3",
			RoleID: "ROLE0000000002",
			Status: domain.RecordActive,
		},
	}

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

	tests := []struct {
		name      string
		roleID    string
		wantCount int
	}{
		{
			name:      "找到多個使用者",
			roleID:    "ROLE0000000001",
			wantCount: 2,
		},
		{
			name:      "找到單一使用者",
			roleID:    "ROLE0000000002",
			wantCount: 1,
		},
		{
			name:      "不存在的角色",
			roleID:    "ROLE9999999999",
			wantCount: 0,
		},
	}

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

			assert.NoError(t, err)
			assert.Len(t, userRoles, tt.wantCount)

			for _, ur := range userRoles {
				assert.Equal(t, tt.roleID, ur.RoleID)
			}
		})
	}
}

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

	// 準備測試數據
	testUserRoles := []*entity.UserRole{
		{
			ID:     bson.NewObjectID(),
			Brand:  "brand1",
			UID:    "user1",
			RoleID: "ROLE0000000001",
			Status: domain.RecordActive,
		},
		{
			ID:     bson.NewObjectID(),
			Brand:  "brand1",
			UID:    "user2",
			RoleID: "ROLE0000000002",
			Status: domain.RecordActive,
		},
		{
			ID:     bson.NewObjectID(),
			Brand:  "brand2",
			UID:    "user3",
			RoleID: "ROLE0000000001",
			Status: domain.RecordInactive,
		},
	}

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

	tests := []struct {
		name      string
		filter    domainRepo.UserRoleFilter
		wantCount int
	}{
		{
			name:      "列出所有使用者角色",
			filter:    domainRepo.UserRoleFilter{},
			wantCount: 3,
		},
		{
			name: "按 Brand 過濾",
			filter: domainRepo.UserRoleFilter{
				Brand: "brand1",
			},
			wantCount: 2,
		},
		{
			name: "按 RoleID 過濾",
			filter: domainRepo.UserRoleFilter{
				RoleID: "ROLE0000000001",
			},
			wantCount: 2,
		},
		{
			name: "組合過濾",
			filter: domainRepo.UserRoleFilter{
				Brand:  "brand1",
				RoleID: "ROLE0000000001",
			},
			wantCount: 1,
		},
	}

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

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

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

	ctx := context.Background()

	// 準備測試數據
	testUserRoles := []*entity.UserRole{
		{ID: bson.NewObjectID(), Brand: "brand1", UID: "user1", RoleID: "ROLE0000000001", Status: domain.RecordActive},
		{ID: bson.NewObjectID(), Brand: "brand1", UID: "user2", RoleID: "ROLE0000000001", Status: domain.RecordActive},
		{ID: bson.NewObjectID(), Brand: "brand1", UID: "user3", RoleID: "ROLE0000000001", Status: domain.RecordActive},
		{ID: bson.NewObjectID(), Brand: "brand2", UID: "user4", RoleID: "ROLE0000000002", Status: domain.RecordActive},
		{ID: bson.NewObjectID(), Brand: "brand2", UID: "user5", RoleID: "ROLE0000000002", Status: domain.RecordActive},
	}

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

	tests := []struct {
		name      string
		roleIDs   []string
		wantCount map[string]int
	}{
		{
			name:    "統計多個角色",
			roleIDs: []string{"ROLE0000000001", "ROLE0000000002"},
			wantCount: map[string]int{
				"ROLE0000000001": 3,
				"ROLE0000000002": 2,
			},
		},
		{
			name:    "統計單一角色",
			roleIDs: []string{"ROLE0000000001"},
			wantCount: map[string]int{
				"ROLE0000000001": 3,
			},
		},
		{
			name:    "統計不存在的角色",
			roleIDs: []string{"ROLE9999999999"},
			wantCount: map[string]int{
				"ROLE9999999999": 0,
			},
		},
		{
			name:    "混合存在和不存在的角色",
			roleIDs: []string{"ROLE0000000001", "ROLE9999999999"},
			wantCount: map[string]int{
				"ROLE0000000001": 3,
				"ROLE9999999999": 0,
			},
		},
	}

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

			assert.NoError(t, err)
			assert.Equal(t, tt.wantCount, counts)
		})
	}
}

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

	ctx := context.Background()

	// 準備測試數據
	testUserRole := &entity.UserRole{
		ID:     bson.NewObjectID(),
		Brand:  "brand1",
		UID:    "user123",
		RoleID: "ROLE0000000001",
		Status: domain.RecordActive,
	}
	err = repo.Create(ctx, testUserRole)
	require.NoError(t, err)

	tests := []struct {
		name   string
		uid    string
		exists bool
	}{
		{
			name:   "存在的使用者角色",
			uid:    "user123",
			exists: true,
		},
		{
			name:   "不存在的使用者",
			uid:    "user999",
			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)
		})
	}
}
