package repository

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

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

	mgo "backend/pkg/library/mongo"

	"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"
)

func setupPermissionRepo(db string) (domainRepo.PermissionRepository, 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 := PermissionRepositoryParam{
		Conf:      conf,
		CacheConf: cacheConf,
		CacheOpts: cacheOpts,
	}
	repo := NewPermissionRepository(param)
	_, _ = repo.Index20251009001UP(context.Background())

	return repo, tearDown, nil
}

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

	ctx := context.Background()
	// 準備測試數據
	testPerm := &entity.Permission{
		ID:    bson.NewObjectID(),
		Name:  "test.permission",
		State: domain.RecordActive,
		Type:  permission.TypeBackend,
	}
	testPerm.CreateTime = time.Now().Unix()
	testPerm.UpdateTime = testPerm.CreateTime

	// 插入測試數據
	_, err = repo.(*PermissionRepository).DB.GetClient().InsertOne(ctx, testPerm)
	require.NoError(t, err)

	tests := []struct {
		name    string
		id      string
		wantErr error
		check   func(*testing.T, *entity.Permission)
	}{
		{
			name:    "找到存在的權限",
			id:      testPerm.ID.Hex(),
			wantErr: nil,
			check: func(t *testing.T, perm *entity.Permission) {
				assert.Equal(t, testPerm.Name, perm.Name)
				assert.Equal(t, testPerm.State, perm.State)
			},
		},
		{
			name:    "無效的 ObjectID",
			id:      "invalid-id",
			wantErr: ErrInvalidObjectID,
			check:   nil,
		},
		{
			name:    "不存在的權限",
			id:      bson.NewObjectID().Hex(),
			wantErr: ErrNotFound,
			check:   nil,
		},
	}

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

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

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

	ctx := context.Background()

	// 準備測試數據
	testPerms := []*entity.Permission{
		{
			ID:    bson.NewObjectID(),
			Name:  "user.list",
			State: domain.RecordActive,
			Type:  permission.TypeBackend,
		},
		{
			ID:    bson.NewObjectID(),
			Name:  "user.create",
			State: domain.RecordActive,
			Type:  permission.TypeBackend,
		},
	}

	for _, perm := range testPerms {
		perm.CreateTime = time.Now().Unix()
		perm.UpdateTime = perm.CreateTime
		_, err := repo.(*PermissionRepository).DB.GetClient().InsertOne(ctx, perm)
		require.NoError(t, err)
	}

	tests := []struct {
		name     string
		permName string
		wantErr  error
		wantName string
	}{
		{
			name:     "找到存在的權限",
			permName: "user.list",
			wantErr:  nil,
			wantName: "user.list",
		},
		{
			name:     "找到另一個權限",
			permName: "user.create",
			wantErr:  nil,
			wantName: "user.create",
		},
		{
			name:     "不存在的權限",
			permName: "user.delete",
			wantErr:  ErrNotFound,
		},
	}

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

			if tt.wantErr != nil {
				assert.ErrorIs(t, err, tt.wantErr)
				assert.Nil(t, perm)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, perm)
				assert.Equal(t, tt.wantName, perm.Name)
			}
		})
	}
}

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

	// 準備測試數據
	testPerms := []*entity.Permission{
		{ID: bson.NewObjectID(), Name: "user.list", State: domain.RecordActive},
		{ID: bson.NewObjectID(), Name: "user.create", State: domain.RecordActive},
		{ID: bson.NewObjectID(), Name: "user.update", State: domain.RecordActive},
	}

	for _, perm := range testPerms {
		perm.CreateTime = time.Now().Unix()
		perm.UpdateTime = perm.CreateTime
		_, err := repo.(*PermissionRepository).DB.GetClient().InsertOne(ctx, perm)
		require.NoError(t, err)
	}

	tests := []struct {
		name      string
		names     []string
		wantCount int
		wantErr   error
	}{
		{
			name:      "找到多個權限",
			names:     []string{"user.list", "user.create"},
			wantCount: 2,
			wantErr:   nil,
		},
		{
			name:      "找到單一權限",
			names:     []string{"user.update"},
			wantCount: 1,
			wantErr:   nil,
		},
		{
			name:      "找到所有權限",
			names:     []string{"user.list", "user.create", "user.update"},
			wantCount: 3,
			wantErr:   nil,
		},
		{
			name:      "部分存在的權限",
			names:     []string{"user.list", "user.delete"},
			wantCount: 1,
			wantErr:   nil,
		},
		{
			name:      "不存在的權限",
			names:     []string{"admin.super"},
			wantCount: 0,
			wantErr:   nil,
		},
	}

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

			if tt.wantErr != nil {
				assert.ErrorIs(t, err, tt.wantErr)
			} else {
				assert.NoError(t, err)
				assert.Len(t, perms, tt.wantCount)
			}
		})
	}
}

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

	ctx := context.Background()

	// 準備測試數據
	testPerms := []*entity.Permission{
		{
			ID:         bson.NewObjectID(),
			Name:       "user.list",
			HTTPPath:   "/api/users",
			HTTPMethod: "GET",
			State:      domain.RecordActive,
		},
		{
			ID:         bson.NewObjectID(),
			Name:       "user.create",
			HTTPPath:   "/api/users",
			HTTPMethod: "POST",
			State:      domain.RecordActive,
		},
	}

	for _, perm := range testPerms {
		perm.CreateTime = time.Now().Unix()
		perm.UpdateTime = perm.CreateTime
		_, err := repo.(*PermissionRepository).DB.GetClient().InsertOne(ctx, perm)
		require.NoError(t, err)
	}

	tests := []struct {
		name     string
		path     string
		method   string
		wantName string
		wantErr  error
	}{
		{
			name:     "找到 GET 權限",
			path:     "/api/users",
			method:   "GET",
			wantName: "user.list",
			wantErr:  nil,
		},
		{
			name:     "找到 POST 權限",
			path:     "/api/users",
			method:   "POST",
			wantName: "user.create",
			wantErr:  nil,
		},
		{
			name:    "不存在的路徑",
			path:    "/api/admin",
			method:  "GET",
			wantErr: ErrNotFound,
		},
		{
			name:    "不存在的方法",
			path:    "/api/users",
			method:  "DELETE",
			wantErr: ErrNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			perm, err := repo.FindByHTTP(ctx, tt.path, tt.method)

			if tt.wantErr != nil {
				assert.ErrorIs(t, err, tt.wantErr)
				assert.Nil(t, perm)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, perm)
				assert.Equal(t, tt.wantName, perm.Name)
				assert.Equal(t, tt.path, perm.HTTPPath)
				assert.Equal(t, tt.method, perm.HTTPMethod)
			}
		})
	}
}

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

	ctx := context.Background()

	// 準備測試數據
	parent := &entity.Permission{
		ID:       bson.NewObjectID(),
		ParentID: bson.ObjectID{},
		Name:     "user",
		State:    domain.RecordActive,
		Type:     permission.TypeBackend,
	}
	parent.CreateTime = time.Now().Unix()
	parent.UpdateTime = parent.CreateTime

	child := &entity.Permission{
		ID:       bson.NewObjectID(),
		ParentID: parent.ID,
		Name:     "user.list",
		State:    domain.RecordActive,
		Type:     permission.TypeBackend,
	}
	child.CreateTime = time.Now().Unix()
	child.UpdateTime = child.CreateTime

	inactiveChild := &entity.Permission{
		ID:       bson.NewObjectID(),
		ParentID: parent.ID,
		Name:     "user.delete",
		State:    domain.RecordInactive,
		Type:     permission.TypeBackend,
	}
	inactiveChild.CreateTime = time.Now().Unix()
	inactiveChild.UpdateTime = inactiveChild.CreateTime

	for _, perm := range []*entity.Permission{parent, child, inactiveChild} {
		_, err := repo.(*PermissionRepository).DB.GetClient().InsertOne(ctx, perm)
		require.NoError(t, err)
	}

	tests := []struct {
		name      string
		filter    domainRepo.PermissionFilter
		wantCount int
		wantNames []string
	}{
		{
			name:      "列出所有權限",
			filter:    domainRepo.PermissionFilter{},
			wantCount: 3,
		},
		{
			name: "只列出啟用的權限",
			filter: domainRepo.PermissionFilter{
				Status: func() *permission.RecordState {
					s := domain.RecordActive
					return &s
				}(),
			},
			wantCount: 2,
			wantNames: []string{"user", "user.list"},
		},
		{
			name: "只列出停用的權限",
			filter: domainRepo.PermissionFilter{
				Status: func() *permission.RecordState {
					s := domain.RecordInactive
					return &s
				}(),
			},
			wantCount: 1,
			wantNames: []string{"user.delete"},
		},
		{
			name: "按類型過濾",
			filter: domainRepo.PermissionFilter{
				Type: func() *permission.Type {
					t := permission.TypeBackend
					return &t
				}(),
			},
			wantCount: 3,
		},
	}

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

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

			if len(tt.wantNames) > 0 {
				names := make([]string, len(perms))
				for i, p := range perms {
					names[i] = p.Name
				}
				for _, wantName := range tt.wantNames {
					assert.Contains(t, names, wantName)
				}
			}
		})
	}
}

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

	ctx := context.Background()

	// 準備測試數據
	activePerms := []*entity.Permission{
		{ID: bson.NewObjectID(), Name: "user.list", State: domain.RecordActive},
		{ID: bson.NewObjectID(), Name: "user.create", State: domain.RecordActive},
	}

	inactivePerms := []*entity.Permission{
		{ID: bson.NewObjectID(), Name: "user.delete", State: domain.RecordInactive},
		{ID: bson.NewObjectID(), Name: "user.admin", State: domain.RecordDeleted},
	}

	allPerms := append(activePerms, inactivePerms...)
	for _, perm := range allPerms {
		perm.CreateTime = time.Now().Unix()
		perm.UpdateTime = perm.CreateTime
		_, err := repo.(*PermissionRepository).DB.GetClient().InsertOne(ctx, perm)
		require.NoError(t, err)
	}

	t.Run("只返回啟用的權限", func(t *testing.T) {
		perms, err := repo.ListActive(ctx)

		assert.NoError(t, err)
		assert.Len(t, perms, 2)

		for _, perm := range perms {
			assert.Equal(t, domain.RecordActive, perm.State)
		}
	})
}
