package repository

import (
	"context"
	"errors"
	"time"

	libmongo "gateway/internal/library/mongo"
	permission "gateway/internal/model/permission/domain"
	"gateway/internal/model/permission/domain/entity"
	"gateway/internal/model/permission/domain/enum"
	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"
)

// PermissionRepositoryParam configures the Mongo permission repository.
type PermissionRepositoryParam struct {
	Conf *libmongo.Conf
}

type permissionRepository struct {
	db libmongo.DocumentDBUseCase
}

// NewPermissionRepository creates a Mongo-backed PermissionRepository.
func NewPermissionRepository(param PermissionRepositoryParam) domrepo.PermissionRepository {
	documentDB, err := libmongo.NewDocumentDB(param.Conf, entity.Permission{}.CollectionName())
	if err != nil {
		panic(err)
	}
	return &permissionRepository{db: documentDB}
}

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

func (r *permissionRepository) UpsertByName(ctx context.Context, perm *entity.Permission) error {
	now := time.Now().UTC().UnixMilli()
	if perm.UpdateAt == 0 {
		perm.UpdateAt = now
	}

	filter := bson.M{permission.BSONFieldName: perm.Name}
	set := bson.M{
		permission.BSONFieldParent:      perm.Parent,
		permission.BSONFieldName:        perm.Name,
		permission.BSONFieldHTTPMethods: perm.HTTPMethods,
		permission.BSONFieldHTTPPath:    perm.HTTPPath,
		permission.BSONFieldStatus:      perm.Status,
		permission.BSONFieldType:        perm.Type,
		permission.BSONFieldUpdateAt:    perm.UpdateAt,
	}
	insert := bson.M{
		permission.BSONFieldCreateAt: now,
	}

	update := bson.M{
		bsonOpSet:         set,
		bsonOpSetOnInsert: insert,
	}
	_, err := r.db.GetClient().UpdateOne(ctx, filter, update, options.UpdateOne().SetUpsert(true))
	return err
}

func (r *permissionRepository) UpdateStatus(ctx context.Context, id string, status enum.Status) error {
	objID, err := bson.ObjectIDFromHex(id)
	if err != nil {
		return permission.ErrPermissionNotFound
	}
	now := time.Now().UTC().UnixMilli()
	filter := bson.M{permission.BSONFieldID: objID}
	set := bson.M{
		permission.BSONFieldStatus:   status,
		permission.BSONFieldUpdateAt: now,
	}
	res, err := r.db.GetClient().UpdateOne(ctx, filter, bson.M{bsonOpSet: set})
	if err != nil {
		return err
	}
	if res.MatchedCount == 0 {
		return permission.ErrPermissionNotFound
	}
	return nil
}

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

func (r *permissionRepository) GetByName(ctx context.Context, name string) (*entity.Permission, error) {
	var doc entity.Permission
	if err := r.db.GetClient().FindOne(ctx, &doc, bson.M{permission.BSONFieldName: name}); err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, permission.ErrPermissionNotFound
		}
		return nil, err
	}
	return &doc, nil
}

func (r *permissionRepository) GetAll(ctx context.Context, status *enum.Status) ([]*entity.Permission, error) {
	q := bson.M{}
	if status != nil {
		q[permission.BSONFieldStatus] = *status
	}
	opts := options.Find().SetSort(bson.D{{Key: permission.BSONFieldName, Value: 1}})
	var docs []*entity.Permission
	if err := r.db.GetClient().Find(ctx, &docs, q, opts); err != nil {
		return nil, err
	}
	return docs, nil
}

func (r *permissionRepository) GetByIDs(ctx context.Context, ids []string) ([]*entity.Permission, 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.BSONFieldID: bson.M{bsonOpIn: objIDs}}
	var docs []*entity.Permission
	if err := r.db.GetClient().Find(ctx, &docs, q); err != nil {
		return nil, err
	}
	return docs, nil
}

func (r *permissionRepository) GetByNames(ctx context.Context, names []string) ([]*entity.Permission, error) {
	if len(names) == 0 {
		return nil, nil
	}
	q := bson.M{permission.BSONFieldName: bson.M{bsonOpIn: names}}
	var docs []*entity.Permission
	if err := r.db.GetClient().Find(ctx, &docs, q); err != nil {
		return nil, err
	}
	return docs, nil
}

// Index20260521001UP ensures permissions collection indexes exist.
func (r *permissionRepository) Index20260521001UP(ctx context.Context) error {
	if err := r.db.PopulateIndex(ctx, permission.BSONFieldName, 1, true); err != nil {
		return err
	}
	if err := r.db.PopulateIndex(ctx, permission.BSONFieldParent, 1, false); err != nil {
		return err
	}
	if err := r.db.PopulateIndex(ctx, permission.BSONFieldStatus, 1, false); err != nil {
		return err
	}
	return r.db.PopulateIndex(ctx, permission.BSONFieldType, 1, false)
}

var _ domrepo.PermissionRepository = (*permissionRepository)(nil)
