package native

import (
	"context"
	"errors"

	"github.com/tech/sendico/pkg/auth/internal/native/nstructures"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mutil/mzap"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
)

// PermissionManager manages permissions using Casbin.
type PermissionManager struct {
	logger   mlogger.Logger
	enforcer *Enforcer
}

// GrantToRole adds a permission to a role in Casbin.
func (m *PermissionManager) GrantToRole(ctx context.Context, policy *model.RolePolicy) error {
	objRef := "any"
	if (policy.ObjectRef != nil) && (*policy.ObjectRef != bson.NilObjectID) {
		objRef = policy.ObjectRef.Hex()
	}

	m.logger.Debug("Granting permission to role", mzap.ObjRef("role_ref", policy.RoleDescriptionRef),
		mzap.ObjRef("permission_ref", policy.DescriptionRef), zap.String("object_ref", objRef),
		zap.String("action", string(policy.Effect.Action)), zap.String("effect", string(policy.Effect.Effect)),
	)

	assignment := nstructures.PolicyAssignment{
		Policy:  policy.Policy,
		RoleRef: policy.RoleDescriptionRef,
	}
	if err := m.enforcer.pdb.Create(ctx, &assignment); err != nil {
		m.logger.Warn("Failed to grant policy", zap.Error(err), mzap.ObjRef("role_ref", policy.RoleDescriptionRef),
			mzap.ObjRef("permission_ref", policy.DescriptionRef), zap.String("object_ref", objRef),
			zap.String("action", string(policy.Effect.Action)), zap.String("effect", string(policy.Effect.Effect)))
		return err
	}
	return nil
}

// RevokeFromRole removes a permission from a role in Casbin.
func (m *PermissionManager) RevokeFromRole(ctx context.Context, policy *model.RolePolicy) error {
	objRef := "*"
	if policy.ObjectRef != nil {
		objRef = policy.ObjectRef.Hex()
	}
	m.logger.Debug("Revoking permission from role", mzap.ObjRef("role_ref", policy.RoleDescriptionRef),
		mzap.ObjRef("permission_ref", policy.DescriptionRef), zap.String("object_ref", objRef),
		zap.String("action", string(policy.Effect.Action)), zap.String("effect", string(policy.Effect.Effect)),
	)
	if err := m.enforcer.pdb.Remove(ctx, policy); err != nil {
		m.logger.Warn("Failed to revoke policy", zap.Error(err), mzap.ObjRef("role_ref", policy.RoleDescriptionRef),
			mzap.ObjRef("permission_ref", policy.DescriptionRef), zap.String("object_ref", objRef),
			zap.String("action", string(policy.Effect.Action)), zap.String("effect", string(policy.Effect.Effect)))
		return err
	}

	return nil
}

// GetPolicies retrieves all policies for a specific role.
func (m *PermissionManager) GetPolicies(
	ctx context.Context,
	roleRef bson.ObjectID,
) ([]model.RolePolicy, error) {
	m.logger.Debug("Fetching policies for role", mzap.ObjRef("role_ref", roleRef))

	assinments, err := m.enforcer.pdb.PoliciesForRole(ctx, roleRef)
	if errors.Is(err, merrors.ErrNoData) {
		m.logger.Debug("No policies found", mzap.ObjRef("role_ref", roleRef))
		return []model.RolePolicy{}, nil
	}
	policies := make([]model.RolePolicy, len(assinments))
	for i, assinment := range assinments {
		policies[i] = model.RolePolicy{
			Policy:             assinment.Policy,
			RoleDescriptionRef: assinment.RoleRef,
		}
	}
	m.logger.Debug("Policies fetched successfully", mzap.ObjRef("role_ref", roleRef), zap.Int("count", len(policies)))
	return policies, nil
}

// Save persists changes to the Casbin policy store.
func (m *PermissionManager) Save() error {
	m.logger.Info("Policies successfully saved") // do nothing
	return nil
}

func NewPermissionManager(logger mlogger.Logger, enforcer *Enforcer) *PermissionManager {
	return &PermissionManager{
		logger:   logger.Named("permission"),
		enforcer: enforcer,
	}
}
