package repository

import (
	"context"
	"errors"
	"time"

	libmongo "gateway/internal/library/mongo"
	permission "gateway/internal/model/permission/domain"
	"gateway/internal/model/permission/domain/entity"
	domrepo "gateway/internal/model/permission/domain/repository"

	"go.mongodb.org/mongo-driver/v2/bson"
	mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

// RoleRepositoryParam configures the Mongo role repository.
type RoleRepositoryParam struct {
	Conf *libmongo.Conf
}

type roleRepository struct {
	db libmongo.DocumentDBUseCase
}

// NewRoleRepository creates a Mongo-backed RoleRepository.
func NewRoleRepository(param RoleRepositoryParam) domrepo.RoleRepository {
	documentDB, err := libmongo.NewDocumentDB(param.Conf, entity.Role{}.CollectionName())
	if err != nil {
		panic(err)
	}
	return &roleRepository{db: documentDB}
}

func (r *roleRepository) Insert(ctx context.Context, role *entity.Role) error {
	now := time.Now().UTC().UnixMilli()
	if role.ID.IsZero() {
		role.ID = bson.NewObjectID()
	}
	if role.CreateAt == 0 {
		role.CreateAt = now
	}
	if role.UpdateAt == 0 {
		role.UpdateAt = now
	}
	_, err := r.db.GetClient().InsertOne(ctx, role)
	if err != nil {
		if mongodriver.IsDuplicateKeyError(err) {
			return permission.ErrRoleDuplicate
		}
		return err
	}
	return nil
}

func (r *roleRepository) GetByID(ctx context.Context, tenantID, id string) (*entity.Role, error) {
	objID, err := bson.ObjectIDFromHex(id)
	if err != nil {
		return nil, permission.ErrRoleNotFound
	}
	var doc entity.Role
	filter := bson.M{
		permission.BSONFieldID:       objID,
		permission.BSONFieldTenantID: tenantID,
	}
	if err := r.db.GetClient().FindOne(ctx, &doc, filter); err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, permission.ErrRoleNotFound
		}
		return nil, err
	}
	return &doc, nil
}

func (r *roleRepository) GetByKey(ctx context.Context, tenantID, key string) (*entity.Role, error) {
	var doc entity.Role
	filter := bson.M{
		permission.BSONFieldTenantID: tenantID,
		permission.BSONFieldKey:      key,
	}
	if err := r.db.GetClient().FindOne(ctx, &doc, filter); err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, permission.ErrRoleNotFound
		}
		return nil, err
	}
	return &doc, nil
}

func (r *roleRepository) ListByTenant(ctx context.Context, tenantID string) ([]*entity.Role, error) {
	q := bson.M{permission.BSONFieldTenantID: tenantID}
	opts := options.Find().SetSort(bson.D{
		{Key: permission.BSONFieldIsSystem, Value: -1},
		{Key: permission.BSONFieldKey, Value: 1},
	})
	var docs []*entity.Role
	if err := r.db.GetClient().Find(ctx, &docs, q, opts); err != nil {
		return nil, err
	}
	return docs, nil
}

func (r *roleRepository) ListByTenantAndIDs(ctx context.Context, tenantID string, ids []string) ([]*entity.Role, error) {
	if len(ids) == 0 {
		return nil, nil
	}
	objIDs := make([]bson.ObjectID, 0, len(ids))
	for _, id := range ids {
		objID, err := bson.ObjectIDFromHex(id)
		if err != nil {
			continue
		}
		objIDs = append(objIDs, objID)
	}
	if len(objIDs) == 0 {
		return nil, nil
	}
	q := bson.M{
		permission.BSONFieldTenantID: tenantID,
		permission.BSONFieldID:       bson.M{bsonOpIn: objIDs},
	}
	var docs []*entity.Role
	if err := r.db.GetClient().Find(ctx, &docs, q); err != nil {
		return nil, err
	}
	return docs, nil
}

func (r *roleRepository) Update(ctx context.Context, tenantID, id string, update *domrepo.RoleUpdate) (*entity.Role, error) {
	if update == nil {
		return r.GetByID(ctx, tenantID, id)
	}
	objID, err := bson.ObjectIDFromHex(id)
	if err != nil {
		return nil, permission.ErrRoleNotFound
	}
	now := time.Now().UTC().UnixMilli()
	set := bson.M{permission.BSONFieldUpdateAt: now}
	if update.DisplayName != nil {
		set[permission.BSONFieldDisplayName] = *update.DisplayName
	}
	if update.Status != nil {
		set[permission.BSONFieldStatus] = *update.Status
	}
	filter := bson.M{
		permission.BSONFieldID:       objID,
		permission.BSONFieldTenantID: tenantID,
	}
	var doc entity.Role
	opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
	if err := r.db.GetClient().FindOneAndUpdate(ctx, &doc, filter, bson.M{bsonOpSet: set}, opts); err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, permission.ErrRoleNotFound
		}
		return nil, err
	}
	return &doc, nil
}

func (r *roleRepository) Delete(ctx context.Context, tenantID, id string) error {
	objID, err := bson.ObjectIDFromHex(id)
	if err != nil {
		return permission.ErrRoleNotFound
	}
	filter := bson.M{
		permission.BSONFieldID:       objID,
		permission.BSONFieldTenantID: tenantID,
	}
	res, err := r.db.GetClient().DeleteOne(ctx, filter)
	if err != nil {
		return err
	}
	if res == 0 {
		return permission.ErrRoleNotFound
	}
	return nil
}

// Index20260521001UP ensures roles collection indexes exist.
func (r *roleRepository) Index20260521001UP(ctx context.Context) error {
	if err := r.db.PopulateMultiIndex(ctx,
		[]string{permission.BSONFieldTenantID, permission.BSONFieldKey},
		[]int32{1, 1}, true); err != nil {
		return err
	}
	return r.db.PopulateMultiIndex(ctx,
		[]string{permission.BSONFieldTenantID, permission.BSONFieldIsSystem},
		[]int32{1, 1}, false)
}

var _ domrepo.RoleRepository = (*roleRepository)(nil)
