package repository

import (
	"backend/pkg/library/mongo"
	"backend/pkg/permission/domain"
	"backend/pkg/permission/domain/entity"
	"backend/pkg/permission/domain/repository"
	"context"
	"errors"
	"strings"

	"github.com/zeromicro/go-zero/core/stores/cache"
	"github.com/zeromicro/go-zero/core/stores/mon"
	"go.mongodb.org/mongo-driver/v2/bson"
	mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
)

type PermissionRepositoryParam struct {
	Conf      *mongo.Conf
	CacheConf cache.CacheConf
	DBOpts    []mon.Option
	CacheOpts []cache.Option
}

type PermissionRepository struct {
	DB mongo.DocumentDBWithCacheUseCase
}

func NewPermissionRepository(param PermissionRepositoryParam) repository.PermissionRepository {
	e := entity.Permission{}
	documentDB, err := mongo.MustDocumentDBWithCache(
		param.Conf,
		e.CollectionName(),
		param.CacheConf,
		param.DBOpts,
		param.CacheOpts,
	)
	if err != nil {
		panic(err)
	}

	return &PermissionRepository{
		DB: documentDB,
	}
}

func (repo *PermissionRepository) FindOne(ctx context.Context, id string) (*entity.Permission, error) {
	var data entity.Permission
	rk := domain.GetPermissionIDRedisKey(id)

	oid, err := bson.ObjectIDFromHex(id)
	if err != nil {
		return nil, ErrInvalidObjectID
	}

	err = repo.DB.FindOne(ctx, rk, &data, bson.M{"_id": oid})
	switch {
	case err == nil:
		return &data, nil
	case errors.Is(err, mon.ErrNotFound):
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (repo *PermissionRepository) FindByName(ctx context.Context, name string) (*entity.Permission, error) {
	var data entity.Permission
	rk := domain.GetPermissionNameRedisKey(name)

	err := repo.DB.FindOne(ctx, rk, &data, bson.M{"name": name})
	switch {
	case err == nil:
		return &data, nil
	case errors.Is(err, mon.ErrNotFound):
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (repo *PermissionRepository) GetByNames(ctx context.Context, names []string) ([]*entity.Permission, error) {
	var data []*entity.Permission

	filter := bson.M{
		"name": bson.M{"$in": names},
	}

	err := repo.DB.GetClient().Find(ctx, &data, filter)
	if err != nil {
		if errors.Is(err, mon.ErrNotFound) {
			return nil, ErrNotFound
		}

		return nil, err
	}

	return data, nil
}

func (repo *PermissionRepository) FindByHTTP(ctx context.Context, path, method string) (*entity.Permission, error) {
	var perm entity.Permission

	filter := bson.M{
		"http_path":   path,
		"http_method": strings.ToUpper(method), // 確保大小寫一致
	}

	err := repo.DB.GetClient().FindOne(ctx, &perm, filter)
	switch {
	case err == nil:
		return &perm, nil
	case errors.Is(err, mon.ErrNotFound):
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (repo *PermissionRepository) List(ctx context.Context, filter repository.PermissionFilter) ([]*entity.Permission, error) {
	var data []*entity.Permission

	// 建立查詢條件
	bsonFilter := bson.M{}

	// 如果有指定類型
	if filter.Type != nil {
		bsonFilter["type"] = *filter.Type
	}

	// 如果有指定狀態
	if filter.Status != nil {
		bsonFilter["status"] = *filter.Status
	}

	// 如果有指定父 ID
	if filter.ParentID != nil {
		if *filter.ParentID == 0 {
			// 查詢根權限 (沒有父 ID 或父 ID 為空)
			bsonFilter["$or"] = []bson.M{
				{"parent_id": bson.M{"$exists": false}},
				{"parent_id": bson.ObjectID{}},
			}
		} else {
			// 查詢特定父 ID 的子權限
			bsonFilter["parent_id"] = *filter.ParentID
		}
	}

	err := repo.DB.GetClient().Find(ctx, &data, bsonFilter)
	if err != nil {
		if errors.Is(err, mon.ErrNotFound) {
			return []*entity.Permission{}, nil
		}
		return nil, err
	}

	return data, nil
}

func (repo *PermissionRepository) ListActive(ctx context.Context) ([]*entity.Permission, error) {
	var data []*entity.Permission
	// 使用快取查詢啟用的權限
	bsonFilter := bson.M{
		"status": domain.RecordActive,
	}

	err := repo.DB.GetClient().Find(ctx, &data, bsonFilter)
	if err != nil {
		if errors.Is(err, mon.ErrNotFound) {
			return []*entity.Permission{}, nil
		}
		return nil, err
	}

	return data, nil
}

func (repo *PermissionRepository) GetChildren(ctx context.Context, parentID int64) ([]*entity.Permission, error) {
	var data []*entity.Permission
	// 查詢指定父 ID 的子權限
	bsonFilter := bson.M{
		"parent_id": parentID,
	}

	err := repo.DB.GetClient().Find(ctx, &data, bsonFilter)
	if err != nil {
		if errors.Is(err, mon.ErrNotFound) {
			return []*entity.Permission{}, nil
		}
		return nil, err
	}

	return data, nil
}

// Index20251009001UP 建立 Permission 集合的索引
// 這個函數應該在應用啟動時或數據庫遷移時執行一次
func (repo *PermissionRepository) Index20251009001UP(ctx context.Context) (*mongodriver.Cursor, error) {
	// 1. 唯一索引：權限名稱必須唯一
	// 等價於 db.permission.createIndex({"name": 1}, {unique: true})
	repo.DB.PopulateIndex(ctx, "name", 1, true)

	// 2. 複合唯一稀疏索引：HTTP 路徑 + 方法的組合必須唯一（用於 API 權限）
	// 等價於 db.permission.createIndex({"http_path": 1, "http_method": 1}, {unique: true, sparse: true})
	// 注意：sparse: true 表示只對存在這些欄位的文檔建立索引，避免 null 值衝突
	repo.DB.PopulateSparseMultiIndex(ctx, []string{"http_path", "http_method"}, []int32{1, 1}, true)

	// 3. 查詢索引：按狀態查詢（例如 ListActive）
	// 等價於 db.permission.createIndex({"status": 1})
	repo.DB.PopulateIndex(ctx, "status", 1, false)

	// 4. 查詢索引：按父 ID 查詢（用於獲取子權限）
	// 等價於 db.permission.createIndex({"parent_id": 1})
	repo.DB.PopulateIndex(ctx, "parent_id", 1, false)

	// 5. 複合索引：按類型和狀態查詢（常用組合）
	// 等價於 db.permission.createIndex({"type": 1, "status": 1})
	repo.DB.PopulateMultiIndex(ctx, []string{"type", "status"}, []int32{1, 1}, false)

	// 6. 時間戳索引：用於排序和時間範圍查詢
	// 等價於 db.permission.createIndex({"create_time": 1})
	repo.DB.PopulateIndex(ctx, "create_time", 1, false)

	// 返回所有索引列表
	return repo.DB.GetClient().Indexes().List(ctx)
}
