-- =============================================================================
-- POLICY DSL TABLE
-- =============================================================================
-- Simplified table for developers to declare policy
-- Uses friendly names instead of UUIDs

CREATE TABLE morbac.policy (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    org_name TEXT NOT NULL,
    role_name TEXT NOT NULL,
    activity TEXT NOT NULL,
    view TEXT NOT NULL,
    modality morbac.modality NOT NULL,
    context_name TEXT NOT NULL DEFAULT 'always',
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    compiled BOOLEAN NOT NULL DEFAULT FALSE,
    UNIQUE(org_name, role_name, activity, view, modality, context_name)
);

CREATE INDEX idx_policy_not_compiled ON morbac.policy(compiled) WHERE NOT compiled;

COMMENT ON TABLE morbac.policy IS 'Policy DSL - simplified policy declaration using names';
COMMENT ON COLUMN morbac.policy.org_name IS 'Organization name (resolved during compilation)';
COMMENT ON COLUMN morbac.policy.role_name IS 'Role name (resolved during compilation)';
COMMENT ON COLUMN morbac.policy.activity IS 'Activity name';
COMMENT ON COLUMN morbac.policy.view IS 'View name';
COMMENT ON COLUMN morbac.policy.modality IS 'Deontic modality';
COMMENT ON COLUMN morbac.policy.context_name IS 'Context name (default: always)';
COMMENT ON COLUMN morbac.policy.compiled IS 'Whether this policy entry has been compiled into rules';

-- Helper function to check administration permissions
CREATE OR REPLACE FUNCTION morbac.is_admin_allowed(
    p_user_id UUID,
    p_org_id UUID,
    p_admin_activity TEXT,
    p_admin_target TEXT
)
RETURNS BOOLEAN
LANGUAGE plpgsql
STABLE
AS $$
DECLARE
    v_rule RECORD;
BEGIN
    -- Check for admin prohibitions first
    FOR v_rule IN
        SELECT ar.context_id
        FROM morbac.admin_rules ar
        WHERE ar.org_id = p_org_id
          AND ar.admin_activity = p_admin_activity
          AND ar.admin_target = p_admin_target
          AND ar.modality = 'prohibition'
          AND ar.role_id IN (
              SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, p_org_id)
          )
    LOOP
        IF morbac.eval_context(v_rule.context_id) THEN
            RETURN FALSE;
        END IF;
    END LOOP;

    -- Check for admin permissions
    FOR v_rule IN
        SELECT ar.context_id
        FROM morbac.admin_rules ar
        WHERE ar.org_id = p_org_id
          AND ar.admin_activity = p_admin_activity
          AND ar.admin_target = p_admin_target
          AND ar.modality = 'permission'
          AND ar.role_id IN (
              SELECT role_id FROM morbac.get_comprehensive_roles(p_user_id, p_org_id)
          )
    LOOP
        IF morbac.eval_context(v_rule.context_id) THEN
            RETURN TRUE;
        END IF;
    END LOOP;

    RETURN FALSE; -- Default deny
END;
$$;

COMMENT ON FUNCTION morbac.is_admin_allowed(UUID, UUID, TEXT, TEXT) IS
'Checks administration permissions for policy management operations';

-- =============================================================================
-- POLICY COMPILER
-- =============================================================================
-- Translates policy DSL entries into concrete rules
-- Resolves names to IDs
-- Idempotent - safe to run multiple times

CREATE OR REPLACE FUNCTION morbac.compile_policy()
RETURNS TABLE(
    compiled_count INTEGER,
    error_count INTEGER,
    errors TEXT[]
)
LANGUAGE plpgsql
AS $$
DECLARE
    v_policy RECORD;
    v_org_id UUID;
    v_role_id UUID;
    v_context_id UUID;
    v_compiled INTEGER := 0;
    v_errors TEXT[] := ARRAY[]::TEXT[];
    v_error_count INTEGER := 0;
BEGIN
    -- Process all uncompiled policy entries
    FOR v_policy IN
        SELECT * FROM morbac.policy WHERE NOT compiled
    LOOP
        BEGIN
            -- Resolve organization
            SELECT id INTO STRICT v_org_id
            FROM morbac.orgs
            WHERE name = v_policy.org_name;

            -- Resolve role within organization
            SELECT id INTO STRICT v_role_id
            FROM morbac.roles
            WHERE org_id = v_org_id AND name = v_policy.role_name;

            -- Resolve context
            SELECT id INTO STRICT v_context_id
            FROM morbac.contexts
            WHERE name = v_policy.context_name;

            -- Ensure activity exists
            INSERT INTO morbac.activities (name)
            VALUES (v_policy.activity)
            ON CONFLICT (name) DO NOTHING;

            -- Ensure view exists
            INSERT INTO morbac.views (name)
            VALUES (v_policy.view)
            ON CONFLICT (name) DO NOTHING;

            -- Insert rule (ignore if already exists)
            INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality)
            VALUES (v_org_id, v_role_id, v_policy.activity, v_policy.view, v_context_id, v_policy.modality)
            ON CONFLICT (org_id, role_id, activity, view, context_id, modality) DO NOTHING;

            -- Mark as compiled
            UPDATE morbac.policy SET compiled = TRUE WHERE id = v_policy.id;

            v_compiled := v_compiled + 1;

        EXCEPTION WHEN OTHERS THEN
            v_error_count := v_error_count + 1;
            v_errors := array_append(v_errors,
                format('Policy %s: %s', v_policy.id, SQLERRM));
        END;
    END LOOP;

    RETURN QUERY SELECT v_compiled, v_error_count, v_errors;
END;
$$;

COMMENT ON FUNCTION morbac.compile_policy() IS
'Compiles policy DSL entries into concrete rules - idempotent and safe to run multiple times';
