package repository

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

	"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"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

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

type UserRoleRepository struct {
	DB mongo.DocumentDBWithCacheUseCase
}

func NewUserRoleRepository(param UserRoleRepositoryParam) repository.UserRoleRepository {
	e := entity.UserRole{}
	documentDB, err := mongo.MustDocumentDBWithCache(
		param.Conf,
		e.CollectionName(),
		param.CacheConf,
		param.DBOpts,
		param.CacheOpts,
	)
	if err != nil {
		panic(err)
	}

	return &UserRoleRepository{
		DB: documentDB,
	}
}

// Create 建立使用者角色
func (repo *UserRoleRepository) Create(ctx context.Context, userRole *entity.UserRole) error {
	if userRole.ID.IsZero() {
		userRole.ID = bson.NewObjectID()
	}

	// 設定時間戳記
	now := time.Now().Unix()
	userRole.CreateTime = now
	userRole.UpdateTime = now

	_, err := repo.DB.GetClient().InsertOne(ctx, userRole)
	if err != nil {
		return err
	}

	// 清除相關快取
	repo.clearUserRoleCache(ctx, userRole.UID)

	return nil
}

// Update 更新使用者角色
func (repo *UserRoleRepository) Update(ctx context.Context, uid, roleID string) (*entity.UserRole, error) {
	now := time.Now().Unix()

	filter := bson.M{"uid": uid}
	update := bson.M{
		"$set": bson.M{
			"role_id":     roleID,
			"update_time": now,
		},
	}

	// 設置選項：返回更新後的文檔
	opts := options.FindOneAndUpdate().SetReturnDocument(options.After)

	var result entity.UserRole
	err := repo.DB.GetClient().FindOneAndUpdate(ctx, &result, filter, update, opts)
	if err != nil {
		if errors.Is(err, mon.ErrNotFound) {
			return nil, ErrNotFound
		}
		return nil, err
	}

	// 清除快取
	repo.clearUserRoleCache(ctx, uid)

	return &result, nil
}

// Delete 刪除使用者角色
func (repo *UserRoleRepository) Delete(ctx context.Context, uid string) error {
	filter := bson.M{"uid": uid}

	_, err := repo.DB.GetClient().DeleteOne(ctx, filter)
	if err != nil {
		return err
	}

	// 清除快取
	repo.clearUserRoleCache(ctx, uid)

	return nil
}

// Get 取得使用者角色
func (repo *UserRoleRepository) Get(ctx context.Context, uid string) (*entity.UserRole, error) {
	var data entity.UserRole
	rk := domain.GetUserRoleUIDRedisKey(uid)

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

// GetByRoleID 根據角色 ID 取得所有使用者
func (repo *UserRoleRepository) GetByRoleID(ctx context.Context, roleID string) ([]*entity.UserRole, error) {
	var data []*entity.UserRole

	filter := bson.M{"role_id": roleID}

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

	return data, nil
}

// List 列出所有使用者角色
func (repo *UserRoleRepository) List(ctx context.Context, filter repository.UserRoleFilter) ([]*entity.UserRole, error) {
	var data []*entity.UserRole

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

	// 如果有指定 Brand
	if filter.Brand != "" {
		bsonFilter["brand"] = filter.Brand
	}

	// 如果有指定 RoleID
	if filter.RoleID != "" {
		bsonFilter["role_id"] = filter.RoleID
	}

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

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

	return data, nil
}

// CountByRoleID 統計每個角色的使用者數量
func (repo *UserRoleRepository) CountByRoleID(ctx context.Context, roleIDs []string) (map[string]int, error) {
	if len(roleIDs) == 0 {
		return make(map[string]int), nil
	}

	// 使用 MongoDB aggregation pipeline 進行分組統計
	pipeline := []bson.M{
		{
			"$match": bson.M{
				"role_id": bson.M{"$in": roleIDs},
			},
		},
		{
			"$group": bson.M{
				"_id":   "$role_id",
				"count": bson.M{"$sum": 1},
			},
		},
	}

	cursor, err := repo.DB.GetClient().Collection.Aggregate(ctx, pipeline)
	if err != nil {
		return nil, err
	}
	defer cursor.Close(ctx)

	// 解析結果
	result := make(map[string]int)
	for cursor.Next(ctx) {
		var item struct {
			ID    string `bson:"_id"`
			Count int    `bson:"count"`
		}
		if err := cursor.Decode(&item); err != nil {
			continue
		}
		result[item.ID] = item.Count
	}

	// 確保所有傳入的 roleID 都有對應的計數（沒有使用者的角色計數為 0）
	for _, roleID := range roleIDs {
		if _, exists := result[roleID]; !exists {
			result[roleID] = 0
		}
	}

	return result, nil
}

// Exists 檢查使用者是否已有角色
func (repo *UserRoleRepository) Exists(ctx context.Context, uid string) (bool, error) {
	count, err := repo.DB.GetClient().CountDocuments(ctx, bson.M{"uid": uid})
	if err != nil {
		return false, err
	}

	return count > 0, nil
}

// clearUserRoleCache 清除使用者角色相關快取
func (repo *UserRoleRepository) clearUserRoleCache(ctx context.Context, uid string) {
	if uid != "" {
		rk := domain.GetUserRoleUIDRedisKey(uid)
		_ = repo.DB.DelCache(ctx, rk)
	}
}

// Index20251009004UP 建立 UserRole 集合的索引
// 這個函數應該在應用啟動時或數據庫遷移時執行一次
func (repo *UserRoleRepository) Index20251009004UP(ctx context.Context) (*mongodriver.Cursor, error) {
	// 1. 唯一索引：使用者 UID 必須唯一（一個使用者只能有一個角色）
	// 等價於 db.user_role.createIndex({"uid": 1}, {unique: true})
	repo.DB.PopulateIndex(ctx, "uid", 1, true)

	// 2. 查詢索引：按角色 ID 查詢（用於獲取某角色的所有使用者）
	// 等價於 db.user_role.createIndex({"role_id": 1})
	repo.DB.PopulateIndex(ctx, "role_id", 1, false)

	// 3. 查詢索引：按 Brand 查詢
	// 等價於 db.user_role.createIndex({"brand": 1})
	repo.DB.PopulateIndex(ctx, "brand", 1, false)

	// 4. 查詢索引：按狀態查詢
	// 等價於 db.user_role.createIndex({"status": 1})
	repo.DB.PopulateIndex(ctx, "status", 1, false)

	// 5. 複合索引：按 Brand 和角色 ID 查詢（常用組合）
	// 等價於 db.user_role.createIndex({"brand": 1, "role_id": 1})
	repo.DB.PopulateMultiIndex(ctx, []string{"brand", "role_id"}, []int32{1, 1}, false)

	// 6. 複合索引：按 Brand 和狀態查詢
	// 等價於 db.user_role.createIndex({"brand": 1, "status": 1})
	repo.DB.PopulateMultiIndex(ctx, []string{"brand", "status"}, []int32{1, 1}, false)

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

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