package db

import (
	"context"

	"github.com/tech/sendico/pkg/auth/internal/native/nstructures"
	"github.com/tech/sendico/pkg/db/repository"
	ri "github.com/tech/sendico/pkg/db/repository/index"
	"github.com/tech/sendico/pkg/db/template"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mservice"
	mutil "github.com/tech/sendico/pkg/mutil/db"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.uber.org/zap"
)

type PermissionsDBImp struct {
	template.DBImp[*nstructures.PolicyAssignment]
}

func (db *PermissionsDBImp) Policies(ctx context.Context, object model.PermissionBoundStorable, action model.Action) ([]nstructures.PolicyAssignment, error) {
	return mutil.GetObjects[nstructures.PolicyAssignment](
		ctx,
		db.Logger,
		repository.Query().And(
			repository.Filter("policy.organizationRef", object.GetOrganizationRef()),
			repository.Filter("policy.descriptionRef", object.GetPermissionRef()),
			repository.Filter("policy.effect.action", action),
			repository.Query().Or(
				repository.Filter("policy.objectRef", *object.GetID()),
				repository.Filter("policy.objectRef", nil),
			),
		),
		nil,
		db.Repository,
	)
}

func (db *PermissionsDBImp) PoliciesForPermissionAction(ctx context.Context, roleRef, permissionRef bson.ObjectID, action model.Action) ([]nstructures.PolicyAssignment, error) {
	return mutil.GetObjects[nstructures.PolicyAssignment](
		ctx,
		db.Logger,
		repository.Query().And(
			repository.Filter("roleRef", roleRef),
			repository.Filter("policy.descriptionRef", permissionRef),
			repository.Filter("policy.effect.action", action),
		),
		nil,
		db.Repository,
	)
}

func (db *PermissionsDBImp) Remove(ctx context.Context, policy *model.RolePolicy) error {
	objRefFilter := repository.Query().Or(
		repository.Filter("policy.objectRef", nil),
		repository.Filter("policy.objectRef", bson.NilObjectID),
	)
	if policy.ObjectRef != nil {
		objRefFilter = repository.Filter("policy.objectRef", *policy.ObjectRef)
	}
	return db.Repository.DeleteMany(
		ctx,
		repository.Query().And(
			repository.Filter("roleRef", policy.RoleDescriptionRef),
			repository.Filter("policy.organizationRef", policy.OrganizationRef),
			repository.Filter("policy.descriptionRef", policy.DescriptionRef),
			objRefFilter,
			repository.Filter("policy.effect.action", policy.Effect.Action),
			repository.Filter("policy.effect.effect", policy.Effect.Effect),
		),
	)
}

func (db *PermissionsDBImp) PoliciesForRole(ctx context.Context, roleRef bson.ObjectID) ([]nstructures.PolicyAssignment, error) {
	return mutil.GetObjects[nstructures.PolicyAssignment](
		ctx,
		db.Logger,
		repository.Filter("roleRef", roleRef),
		nil,
		db.Repository,
	)
}

func (db *PermissionsDBImp) PoliciesForRoles(ctx context.Context, roleRefs []bson.ObjectID, action model.Action) ([]nstructures.PolicyAssignment, error) {
	if len(roleRefs) == 0 {
		db.Logger.Debug("Empty role references list provided, returning empty resposnse")
		return []nstructures.PolicyAssignment{}, nil
	}
	return mutil.GetObjects[nstructures.PolicyAssignment](
		ctx,
		db.Logger,
		repository.Query().And(
			repository.Query().In(repository.Field("roleRef"), roleRefs),
			repository.Filter("policy.effect.action", action),
		),
		nil,
		db.Repository,
	)
}

func NewPoliciesDB(logger mlogger.Logger, db *mongo.Database) (*PermissionsDBImp, error) {
	p := &PermissionsDBImp{
		DBImp: *template.Create[*nstructures.PolicyAssignment](logger, mservice.PolicyAssignements, db),
	}

	// faster
	// harder
	// index
	policiesQueryIndex := &ri.Definition{
		Keys: []ri.Key{
			{Field: "policy.organizationRef", Sort: ri.Asc},
			{Field: "policy.descriptionRef", Sort: ri.Asc},
			{Field: "policy.effect.action", Sort: ri.Asc},
			{Field: "policy.objectRef", Sort: ri.Asc},
		},
	}
	if err := p.Repository.CreateIndex(policiesQueryIndex); err != nil {
		p.Logger.Warn("Failed to prepare policies query index", zap.Error(err))
		return nil, err
	}

	roleBasedQueriesIndex := &ri.Definition{
		Keys: []ri.Key{
			{Field: "roleRef", Sort: ri.Asc},
			{Field: "policy.effect.action", Sort: ri.Asc},
		},
	}
	if err := p.Repository.CreateIndex(roleBasedQueriesIndex); err != nil {
		p.Logger.Warn("Failed to prepare role based query index", zap.Error(err))
		return nil, err
	}

	uniquePolicyConstaint := &ri.Definition{
		Keys: []ri.Key{
			{Field: "policy.organizationRef", Sort: ri.Asc},
			{Field: "roleRef", Sort: ri.Asc},
			{Field: "policy.descriptionRef", Sort: ri.Asc},
			{Field: "policy.effect.action", Sort: ri.Asc},
			{Field: "policy.objectRef", Sort: ri.Asc},
		},
		Unique: true,
	}
	if err := p.Repository.CreateIndex(uniquePolicyConstaint); err != nil {
		p.Logger.Warn("Failed to unique policy assignment index", zap.Error(err))
		return nil, err
	}

	return p, nil
}
