-- =============================================================================
-- Delegation Tests
-- =============================================================================
-- Tests the role delegation feature:
--   - Active delegation grants the delegate the delegator's role permissions
--   - Expired delegations are inactive
--   - Future delegations are not yet active
--   - Revoked delegations are inactive
--   - Delegator must hold the role (delegation from source is validated)
--   - Negative role assignments override delegations
--   - Delegated roles participate in role hierarchy
--   - get_comprehensive_roles reports delegation source
--
-- Scenario:
--   Carol (manager) delegates her manager role to Leo (employee) for 1 day
--   -> Leo should temporarily have manager-level access
--
-- Prerequisites: 00_setup.sql -> 02_hierarchies.sql
-- =============================================================================

\echo ''
\echo '================================================================'
\echo '03 — DELEGATION'
\echo '================================================================'

-- ---------------------------------------------------------------------------
-- Section 1: Baseline — Leo (employee) before any delegation
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 1. Baseline: Leo before delegation ---'

-- Leo has only employee permissions
SELECT morbac.t('Leo (employee) reads documents before delegation',
    morbac.is_allowed_nocache(
        '30000000-0000-0000-0000-000000000012'::uuid,
        '10000000-0000-0000-0000-000000000001'::uuid,
        'read', 'documents'
    ), TRUE);

-- Leo cannot approve documents — that requires manager role
SELECT morbac.t('Leo (employee) approves documents before delegation [no manager perm]',
    morbac.is_allowed_nocache(
        '30000000-0000-0000-0000-000000000012'::uuid,
        '10000000-0000-0000-0000-000000000001'::uuid,
        'approve', 'documents'
    ), FALSE);

-- Leo cannot delete documents (no employee delete perm, activity hierarchy grants delete via read rule,
-- but 'delete' implies only that if there's a delete->write->read chain; in 02 we set write->read but
-- the chain delete->write was also set. So Leo gets delete via documents perm. Let's check:
-- Actually with activity hierarchy (delete->write->read), the 'read documents' rule covers delete.
SELECT morbac.t('Leo (employee) deletes documents before delegation [via activity hierarchy]',
    morbac.is_allowed_nocache(
        '30000000-0000-0000-0000-000000000012'::uuid,
        '10000000-0000-0000-0000-000000000001'::uuid,
        'delete', 'documents'
    ), TRUE);

-- Leo cannot approve documents (approve is not in activity hierarchy for documents)
-- This is the key baseline check: approve requires manager-level permission
SELECT morbac.t('Leo (employee) does not have approve permission before delegation',
    morbac.is_allowed_nocache(
        '30000000-0000-0000-0000-000000000012'::uuid,
        '10000000-0000-0000-0000-000000000001'::uuid,
        'approve', 'documents'
    ), FALSE);

-- ---------------------------------------------------------------------------
-- Section 2: Active delegation — Carol delegates manager role to Leo
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 2. Active delegation: Carol -> Leo (manager role, 1 day) ---'

INSERT INTO morbac.delegations
    (id, delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until)
VALUES (
    'de000001-0000-0000-0000-000000000001',
    '30000000-0000-0000-0000-000000000003', -- Carol (delegator, holds manager)
    '30000000-0000-0000-0000-000000000012', -- Leo (delegatee)
    '20000000-0001-0000-0000-000000000003', -- manager role
    '10000000-0000-0000-0000-000000000001',
    now() - interval '1 hour',
    now() + interval '1 day'
);

-- Leo now has delegated manager role — can approve documents
SELECT morbac.t('Leo (delegated manager) approves documents',
    morbac.is_allowed_nocache(
        '30000000-0000-0000-0000-000000000012'::uuid,
        '10000000-0000-0000-0000-000000000001'::uuid,
        'approve', 'documents'
    ), TRUE);

-- Leo can delete documents (manager permission)
SELECT morbac.t('Leo (delegated manager) deletes documents',
    morbac.is_allowed_nocache(
        '30000000-0000-0000-0000-000000000012'::uuid,
        '10000000-0000-0000-0000-000000000001'::uuid,
        'delete', 'documents'
    ), TRUE);

-- Leo (delegated manager) inherits manager's inherited permissions:
-- manager->employee->intern chain; employee has read documents -> view hierarchy covers financial_data
SELECT morbac.t('Leo (delegated manager) reads financial_data via manager+view hierarchy',
    morbac.is_allowed_nocache(
        '30000000-0000-0000-0000-000000000012'::uuid,
        '10000000-0000-0000-0000-000000000001'::uuid,
        'read', 'financial_data'
    ), TRUE);

-- get_comprehensive_roles should show manager role with delegation source
SELECT morbac.t('Leo has delegated manager role in comprehensive roles',
    EXISTS(
        SELECT 1 FROM morbac.get_comprehensive_roles(
            '30000000-0000-0000-0000-000000000012'::uuid,
            '10000000-0000-0000-0000-000000000001'::uuid
        )
        WHERE role_id = '20000000-0001-0000-0000-000000000003'
          AND source LIKE 'delegation%'
    ), TRUE);

-- Employee role appears as direct in comprehensive roles
SELECT morbac.t('Leo has employee role as direct in comprehensive roles',
    EXISTS(
        SELECT 1 FROM morbac.get_comprehensive_roles(
            '30000000-0000-0000-0000-000000000012'::uuid,
            '10000000-0000-0000-0000-000000000001'::uuid
        )
        WHERE role_id = '20000000-0001-0000-0000-000000000004'
          AND source = 'direct'
    ), TRUE);

-- ---------------------------------------------------------------------------
-- Section 3: Expired delegation
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 3. Expired delegation ---'

INSERT INTO morbac.delegations
    (id, delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until)
VALUES (
    'de000001-0000-0000-0000-000000000002',
    '30000000-0000-0000-0000-000000000008', -- Heidi (auditor)
    '30000000-0000-0000-0000-000000000004', -- Dave
    '20000000-0001-0000-0000-000000000006', -- auditor role
    '10000000-0000-0000-0000-000000000001',
    now() - interval '7 days',
    now() - interval '1 second'  -- expired 1 second ago
);

-- Dave should NOT get auditor permissions from expired delegation
SELECT morbac.t('Dave via expired auditor delegation reads audit_logs [delegation expired]',
    morbac.is_allowed_nocache(
        '30000000-0000-0000-0000-000000000004'::uuid,
        '10000000-0000-0000-0000-000000000001'::uuid,
        'read', 'audit_logs'
    ), FALSE);

-- The expired delegation should NOT appear in comprehensive roles
SELECT morbac.t('Expired auditor delegation not in Dave comprehensive roles',
    NOT EXISTS(
        SELECT 1 FROM morbac.get_comprehensive_roles(
            '30000000-0000-0000-0000-000000000004'::uuid,
            '10000000-0000-0000-0000-000000000001'::uuid
        )
        WHERE role_id = '20000000-0001-0000-0000-000000000006'
          AND source LIKE 'delegation%'
    ), TRUE);

-- ---------------------------------------------------------------------------
-- Section 4: Future delegation (not yet active)
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 4. Future delegation (not yet active) ---'

INSERT INTO morbac.delegations
    (id, delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until)
VALUES (
    'de000001-0000-0000-0000-000000000003',
    '30000000-0000-0000-0000-000000000007', -- Grace (hr_manager)
    '30000000-0000-0000-0000-000000000004', -- Dave
    '20000000-0001-0000-0000-000000000008', -- hr_manager role
    '10000000-0000-0000-0000-000000000001',
    now() + interval '1 day',       -- starts tomorrow
    now() + interval '8 days'
);

-- Dave should NOT have hr_manager in his comprehensive roles (delegation not yet active)
-- Note: employee already has write hr_data via view hierarchy (hr_data->documents);
-- so we test comprehensive roles rather than is_allowed to verify the delegation is inactive.
SELECT morbac.t('Future hr_manager delegation not yet in Dave comprehensive roles',
    NOT EXISTS(
        SELECT 1 FROM morbac.get_comprehensive_roles(
            '30000000-0000-0000-0000-000000000004'::uuid,
            '10000000-0000-0000-0000-000000000001'::uuid
        )
        WHERE role_id = '20000000-0001-0000-0000-000000000008' -- hr_manager
          AND source LIKE 'delegation%'
    ), TRUE);

-- ---------------------------------------------------------------------------
-- Section 5: Revoked delegation
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 5. Revoked delegation ---'

INSERT INTO morbac.delegations
    (id, delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until, revoked)
VALUES (
    'de000001-0000-0000-0000-000000000004',
    '30000000-0000-0000-0000-000000000009', -- Ivan (accountant)
    '30000000-0000-0000-0000-000000000004', -- Dave
    '20000000-0001-0000-0000-000000000007', -- accountant role
    '10000000-0000-0000-0000-000000000001',
    now() - interval '1 hour',
    now() + interval '1 day',
    TRUE  -- revoked
);

-- Dave should NOT get accountant from revoked delegation — verify revoked flag is set
-- Note: employee already has write financial_data via view hierarchy;
-- so we verify the delegation is actually revoked in the DB.
SELECT morbac.t('Revoked accountant delegation has revoked=TRUE in DB',
    (SELECT revoked FROM morbac.delegations
     WHERE id = 'de000001-0000-0000-0000-000000000004'),
    TRUE);

-- The revoked delegation should NOT appear in comprehensive roles
SELECT morbac.t('Revoked accountant delegation not in Dave comprehensive roles',
    NOT EXISTS(
        SELECT 1 FROM morbac.get_comprehensive_roles(
            '30000000-0000-0000-0000-000000000004'::uuid,
            '10000000-0000-0000-0000-000000000001'::uuid
        )
        WHERE role_id = '20000000-0001-0000-0000-000000000007'
          AND source LIKE 'delegation%'
    ), TRUE);

-- ---------------------------------------------------------------------------
-- Section 6: Delegation blocked by negative role assignment
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 6. Negative role assignment overrides delegation ---'

-- Leo has an active delegation for manager role (from section 2)
-- Now add a negative assignment to block that same role
INSERT INTO morbac.negative_role_assignments (user_id, role_id, org_id, reason)
VALUES (
    '30000000-0000-0000-0000-000000000012', -- Leo
    '20000000-0001-0000-0000-000000000003', -- manager role
    '10000000-0000-0000-0000-000000000001',
    'Temporarily suspended from manager responsibilities'
);

-- negative assignment blocks the delegated manager role
SELECT morbac.t('Leo (delegated manager, but negatively assigned) approves documents [blocked]',
    morbac.is_allowed_nocache(
        '30000000-0000-0000-0000-000000000012'::uuid,
        '10000000-0000-0000-0000-000000000001'::uuid,
        'approve', 'documents'
    ), FALSE);

-- Leo's own employee permissions still work (negative is only for manager)
SELECT morbac.t('Leo (employee, manager negated) reads documents [employee perm unaffected]',
    morbac.is_allowed_nocache(
        '30000000-0000-0000-0000-000000000012'::uuid,
        '10000000-0000-0000-0000-000000000001'::uuid,
        'read', 'documents'
    ), TRUE);

-- Manager delegation no longer appears in comprehensive roles (blocked by negative)
SELECT morbac.t('Negated manager delegation not in Leo comprehensive roles',
    NOT EXISTS(
        SELECT 1 FROM morbac.get_comprehensive_roles(
            '30000000-0000-0000-0000-000000000012'::uuid,
            '10000000-0000-0000-0000-000000000001'::uuid
        )
        WHERE role_id = '20000000-0001-0000-0000-000000000003'
    ), TRUE);

-- Clean up negative assignment so subsequent tests aren't affected
DELETE FROM morbac.negative_role_assignments
WHERE user_id = '30000000-0000-0000-0000-000000000012'
  AND role_id = '20000000-0001-0000-0000-000000000003'
  AND org_id  = '10000000-0000-0000-0000-000000000001';

-- ---------------------------------------------------------------------------
-- Section 7: Delegator must hold the role (validation check)
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 7. Delegator-role validation ---'

-- Create a delegation where the delegator does NOT hold the role
-- Dave (employee) tries to delegate the manager role (which he does not have)
INSERT INTO morbac.delegations
    (id, delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until)
VALUES (
    'de000001-0000-0000-0000-000000000005',
    '30000000-0000-0000-0000-000000000004', -- Dave (employee — does NOT hold manager)
    '30000000-0000-0000-0000-000000000005', -- Eve
    '20000000-0001-0000-0000-000000000003', -- manager role
    '10000000-0000-0000-0000-000000000001',
    now() - interval '1 hour',
    now() + interval '1 day'
);

-- get_comprehensive_roles requires delegator to hold the role
-- Eve does NOT get manager access from invalid delegation
SELECT morbac.t('Eve via invalid delegation (Dave does not hold manager) denied approve',
    morbac.is_allowed_nocache(
        '30000000-0000-0000-0000-000000000005'::uuid,
        '10000000-0000-0000-0000-000000000001'::uuid,
        'approve', 'documents'
    ), FALSE);

-- The invalid delegation should NOT appear in Eve's comprehensive roles
SELECT morbac.t('Invalid delegation not in Eve comprehensive roles',
    NOT EXISTS(
        SELECT 1 FROM morbac.get_comprehensive_roles(
            '30000000-0000-0000-0000-000000000005'::uuid,
            '10000000-0000-0000-0000-000000000001'::uuid
        )
        WHERE role_id = '20000000-0001-0000-0000-000000000003'
          AND source LIKE 'delegation%'
    ), TRUE);

-- ---------------------------------------------------------------------------
-- Section 8: Comprehensive roles source reporting
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 8. get_comprehensive_roles source reporting ---'

-- Alice (CEO, direct) — source should be 'direct'
SELECT morbac.t('Alice CEO role has source=direct in comprehensive roles',
    EXISTS(
        SELECT 1 FROM morbac.get_comprehensive_roles(
            '30000000-0000-0000-0000-000000000001'::uuid,
            '10000000-0000-0000-0000-000000000001'::uuid
        )
        WHERE role_id = '20000000-0001-0000-0000-000000000001'
          AND source = 'direct'
    ), TRUE);

-- Alice has no delegation-sourced roles
SELECT morbac.t('Alice has no delegation-sourced roles',
    NOT EXISTS(
        SELECT 1 FROM morbac.get_comprehensive_roles(
            '30000000-0000-0000-0000-000000000001'::uuid,
            '10000000-0000-0000-0000-000000000001'::uuid
        )
        WHERE source LIKE 'delegation%'
    ), TRUE);

\echo ''
\echo '=== Delegation Tests Completed ==='
