package casbin

import (
	"context"

	"github.com/tech/sendico/pkg/auth/anyobject"
	"github.com/tech/sendico/pkg/auth/internal/casbin/serialization"
	"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"
)

// CasbinPermissionManager manages permissions using Casbin.
type CasbinPermissionManager struct {
	logger     mlogger.Logger       // Logger for logging operations
	enforcer   *CasbinEnforcer      // Casbin enforcer for managing policies
	serializer serialization.Policy // Serializer for converting policies to/from Casbin
}

// GrantToRole adds a permission to a role in Casbin.
func (m *CasbinPermissionManager) GrantToRole(ctx context.Context, policy *model.RolePolicy) error {
	objRef := anyobject.ID
	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)),
	)

	// Serialize permission
	serializedPolicy, err := m.serializer.Serialize(policy)
	if err != nil {
		m.logger.Error("Failed to serialize permission while granting permission", zap.Error(err),
			mzap.ObjRef("role_ref", policy.RoleDescriptionRef),
			mzap.ObjRef("permission_ref", policy.DescriptionRef),
			mzap.ObjRef("organization_ref", policy.OrganizationRef),
		)
		return err
	}

	// Add policy to Casbin
	added, err := m.enforcer.enforcer.AddPolicy(serializedPolicy...)
	if err != nil {
		m.logger.Error("Failed to add policy to Casbin", zap.Error(err))
		return err
	}
	if added {
		m.logger.Info("Policy added to Casbin",
			mzap.ObjRef("role_ref", policy.RoleDescriptionRef),
			mzap.ObjRef("permission_ref", policy.DescriptionRef),
			zap.String("object_ref", objRef),
		)
	} else {
		m.logger.Warn("Policy already exists in Casbin",
			mzap.ObjRef("role_ref", policy.RoleDescriptionRef),
			mzap.ObjRef("permission_ref", policy.DescriptionRef),
			zap.String("object_ref", objRef),
		)
	}

	return nil
}

// RevokeFromRole removes a permission from a role in Casbin.
func (m *CasbinPermissionManager) RevokeFromRole(ctx context.Context, policy *model.RolePolicy) error {
	objRef := anyobject.ID
	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)),
	)

	// Serialize policy
	serializedPolicy, err := m.serializer.Serialize(policy)
	if err != nil {
		m.logger.Error("Failed to serialize policy while revoking permission from role",
			zap.Error(err), mzap.ObjRef("role_ref", policy.RoleDescriptionRef),
			mzap.ObjRef("policy_ref", policy.DescriptionRef))
		return err
	}

	// Remove policy from Casbin
	removed, err := m.enforcer.enforcer.RemovePolicy(serializedPolicy...)
	if err != nil {
		m.logger.Error("Failed to remove policy from Casbin", zap.Error(err))
		return err
	}
	if removed {
		m.logger.Info("Policy removed from Casbin",
			mzap.ObjRef("role_ref", policy.RoleDescriptionRef),
			mzap.ObjRef("permission_ref", policy.DescriptionRef),
			zap.String("object_ref", objRef),
		)
	} else {
		m.logger.Warn("Policy does not exist in Casbin",
			mzap.ObjRef("role_ref", policy.RoleDescriptionRef),
			mzap.ObjRef("permission_ref", policy.DescriptionRef),
			zap.String("object_ref", objRef),
		)
	}

	return nil
}

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

	// Retrieve Casbin policies for the role
	policies, err := m.enforcer.enforcer.GetFilteredPolicy(0, roleRef.Hex())
	if err != nil {
		m.logger.Warn("Failed to get policies", zap.Error(err), mzap.ObjRef("role_ref", roleRef))
		return nil, err
	}
	if len(policies) == 0 {
		m.logger.Info("No policies found for role", mzap.ObjRef("role_ref", roleRef))
		return nil, merrors.NoData("no policies")
	}

	// Deserialize policies
	var result []model.RolePolicy
	for _, policy := range policies {
		permission, err := m.serializer.Deserialize(policy)
		if err != nil {
			m.logger.Warn("Failed to deserialize policy", zap.Error(err), zap.String("policy", policy[0]))
			continue
		}
		result = append(result, *permission)
	}

	m.logger.Debug("Policies fetched successfully", mzap.ObjRef("role_ref", roleRef), zap.Int("count", len(result)))
	return result, nil
}

// Save persists changes to the Casbin policy store.
func (m *CasbinPermissionManager) Save() error {
	if err := m.enforcer.enforcer.SavePolicy(); err != nil {
		m.logger.Error("Failed to save policies in Casbin", zap.Error(err))
		return err
	}
	m.logger.Info("Policies successfully saved in Casbin")
	return nil
}

func NewPermissionManager(logger mlogger.Logger, enforcer *CasbinEnforcer) *CasbinPermissionManager {
	return &CasbinPermissionManager{
		logger:     logger.Named("permission"),
		enforcer:   enforcer,
		serializer: serialization.NewPolicySerializer(),
	}
}
