begin;
select plan(71); -- Total number of tests (54 original + 9 multiple notifications tests + 2 invite edge cases + 6 null actor tests)

-- ============================================================================
-- Table Existence Tests
-- ============================================================================

SELECT has_table('public', 'notifications', 'notifications table should exist');

-- ============================================================================
-- Notifications Table Structure
-- ============================================================================

SELECT has_column('public', 'notifications', 'id', 'notifications should have id column');
SELECT has_column('public', 'notifications', 'user_id', 'notifications should have user_id column');
SELECT has_column('public', 'notifications', 'actor_id', 'notifications should have actor_id column');
SELECT has_column('public', 'notifications', 'entity_type', 'notifications should have entity_type column');
SELECT has_column('public', 'notifications', 'entity_id', 'notifications should have entity_id column');
SELECT has_column('public', 'notifications', 'action_type', 'notifications should have action_type column');
SELECT has_column('public', 'notifications', 'message', 'notifications should have message column');
SELECT has_column('public', 'notifications', 'metadata', 'notifications should have metadata column');
SELECT has_column('public', 'notifications', 'read_at', 'notifications should have read_at column');
SELECT has_column('public', 'notifications', 'created_at', 'notifications should have created_at column');

SELECT col_type_is('public', 'notifications', 'id', 'uuid', 'notifications.id should be uuid');
SELECT col_type_is('public', 'notifications', 'user_id', 'uuid', 'notifications.user_id should be uuid');
SELECT col_type_is('public', 'notifications', 'actor_id', 'uuid', 'notifications.actor_id should be uuid');
SELECT col_type_is('public', 'notifications', 'entity_type', 'text', 'notifications.entity_type should be text');
SELECT col_type_is('public', 'notifications', 'entity_id', 'text', 'notifications.entity_id should be text');
SELECT col_type_is('public', 'notifications', 'action_type', 'text', 'notifications.action_type should be text');
SELECT col_type_is('public', 'notifications', 'message', 'jsonb', 'notifications.message should be jsonb');
SELECT col_type_is('public', 'notifications', 'metadata', 'jsonb', 'notifications.metadata should be jsonb');
SELECT col_type_is('public', 'notifications', 'read_at', 'timestamp with time zone', 'notifications.read_at should be timestamptz');
SELECT col_type_is('public', 'notifications', 'created_at', 'timestamp with time zone', 'notifications.created_at should be timestamptz');

SELECT col_not_null('public', 'notifications', 'id', 'notifications.id should be NOT NULL');
SELECT col_not_null('public', 'notifications', 'user_id', 'notifications.user_id should be NOT NULL');
SELECT col_not_null('public', 'notifications', 'entity_type', 'notifications.entity_type should be NOT NULL');
SELECT col_not_null('public', 'notifications', 'entity_id', 'notifications.entity_id should be NOT NULL');
SELECT col_not_null('public', 'notifications', 'action_type', 'notifications.action_type should be NOT NULL');
SELECT col_not_null('public', 'notifications', 'message', 'notifications.message should be NOT NULL');
SELECT col_not_null('public', 'notifications', 'created_at', 'notifications.created_at should be NOT NULL');

SELECT col_has_default('public', 'notifications', 'id', 'notifications.id should have default');
SELECT col_has_default('public', 'notifications', 'metadata', 'notifications.metadata should have default');
SELECT col_has_default('public', 'notifications', 'created_at', 'notifications.created_at should have default');

-- Check action_type constraint
SELECT ok(
    (SELECT COUNT(*) FROM information_schema.check_constraints 
     WHERE constraint_schema = 'public' 
     AND constraint_name LIKE '%action_type%') > 0,
    'notifications.action_type should have check constraint'
);

-- ============================================================================
-- Index Existence Tests
-- ============================================================================

SELECT ok(
    (SELECT COUNT(*) FROM pg_indexes 
     WHERE schemaname = 'public' 
     AND tablename = 'notifications' 
     AND indexname = 'idx_notifications_user_id') > 0,
    'Index idx_notifications_user_id should exist'
);

SELECT ok(
    (SELECT COUNT(*) FROM pg_indexes 
     WHERE schemaname = 'public' 
     AND tablename = 'notifications' 
     AND indexname = 'idx_notifications_entity_type') > 0,
    'Index idx_notifications_entity_type should exist'
);

SELECT ok(
    (SELECT COUNT(*) FROM pg_indexes 
     WHERE schemaname = 'public' 
     AND tablename = 'notifications' 
     AND indexname = 'idx_notifications_entity_id') > 0,
    'Index idx_notifications_entity_id should exist'
);

SELECT ok(
    (SELECT COUNT(*) FROM pg_indexes 
     WHERE schemaname = 'public' 
     AND tablename = 'notifications' 
     AND indexname = 'idx_notifications_read_at') > 0,
    'Index idx_notifications_read_at should exist'
);

SELECT ok(
    (SELECT COUNT(*) FROM pg_indexes 
     WHERE schemaname = 'public' 
     AND tablename = 'notifications' 
     AND indexname = 'idx_notifications_created_at') > 0,
    'Index idx_notifications_created_at should exist'
);

-- ============================================================================
-- Trigger Function Tests
-- ============================================================================

SELECT has_function('public', 'notify_users', 'Function notify_users should exist');

-- ============================================================================
-- Trigger Existence Tests
-- ============================================================================

SELECT has_trigger('public', 'tablos', 'notify_users_on_tablos',
    'Trigger notify_users_on_tablos should exist on tablos table');

SELECT has_trigger('public', 'tasks', 'notify_users_on_tasks',
    'Trigger notify_users_on_tasks should exist on tasks table');

SELECT has_trigger('public', 'events', 'notify_users_on_events',
    'Trigger notify_users_on_events should exist on events table');

SELECT has_trigger('public', 'notes', 'notify_users_on_notes',
    'Trigger notify_users_on_notes should exist on notes table');

SELECT has_trigger('public', 'tablo_access', 'notify_users_on_tablo_access',
    'Trigger notify_users_on_tablo_access should exist on tablo_access table');

SELECT has_trigger('public', 'tablo_invites', 'notify_users_on_tablo_invites',
    'Trigger notify_users_on_tablo_invites should exist on tablo_invites table');

-- ============================================================================
-- Trigger Behavior Tests - Setup Test Data
-- ============================================================================

DO $$
DECLARE
    test_user1_id uuid := gen_random_uuid();
    test_user2_id uuid := gen_random_uuid();
    test_tablo_id text;
    test_task_id text;
    test_event_id text;
    test_note_id text;
BEGIN
    -- Insert test users
    INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
    VALUES 
        (test_user1_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notiftest1_' || test_user1_id::text || '@test.com', 'encrypted', now(), now(), now()),
        (test_user2_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notiftest2_' || test_user2_id::text || '@test.com', 'encrypted', now(), now(), now())
    ON CONFLICT DO NOTHING;
    
    -- Insert test profiles
    INSERT INTO public.profiles (id, email, first_name, last_name, short_user_id)
    VALUES 
        (test_user1_id, 'notiftest1_' || test_user1_id::text || '@test.com', 'NotifTest', 'User1', substring(test_user1_id::text from 1 for 8)),
        (test_user2_id, 'notiftest2_' || test_user2_id::text || '@test.com', 'NotifTest', 'User2', substring(test_user2_id::text from 1 for 8))
    ON CONFLICT DO NOTHING;
    
    -- Set auth context to user1
    PERFORM set_config('request.jwt.claims', json_build_object('sub', test_user1_id::text)::text, true);
    
    -- Create a tablo as user1
    INSERT INTO public.tablos (owner_id, name, status, position)
    VALUES (test_user1_id, 'Notification Test Tablo', 'todo', 0)
    RETURNING id INTO test_tablo_id;
    
    -- Grant access to user2
    INSERT INTO public.tablo_access (tablo_id, user_id, granted_by, is_active, is_admin)
    VALUES (test_tablo_id, test_user2_id, test_user1_id, true, false);
    
    -- Create a task
    INSERT INTO public.tasks (tablo_id, title, description, status, assignee_id, position)
    VALUES (test_tablo_id, 'Test Task', 'Test Description', 'todo', test_user2_id, 0)
    RETURNING id INTO test_task_id;
    
    -- Create an event
    INSERT INTO public.events (tablo_id, title, description, start_date, start_time, created_by)
    VALUES (test_tablo_id, 'Test Event', 'Test Event Description', CURRENT_DATE, '10:00', test_user1_id)
    RETURNING id INTO test_event_id;
    
    -- Create a note
    INSERT INTO public.notes (user_id, title, content)
    VALUES (test_user1_id, 'Test Note', 'Test Note Content')
    RETURNING id INTO test_note_id;
    
    -- Share note with user2
    INSERT INTO public.note_access (note_id, user_id, is_active)
    VALUES (test_note_id, test_user2_id, true);
    
    -- Store test IDs for use in tests
    PERFORM set_config('test.user1_id', test_user1_id::text, true);
    PERFORM set_config('test.user2_id', test_user2_id::text, true);
    PERFORM set_config('test.tablo_id', test_tablo_id, true);
    PERFORM set_config('test.task_id', test_task_id, true);
    PERFORM set_config('test.event_id', test_event_id, true);
    PERFORM set_config('test.note_id', test_note_id, true);
END $$;

-- ============================================================================
-- Tablo Trigger Tests
-- ============================================================================

-- Update tablo and check notification for user2
DO $$
BEGIN
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
    UPDATE public.tablos 
    SET name = 'Updated Test Tablo'
    WHERE id = current_setting('test.tablo_id');
END $$;

SELECT ok(
    (SELECT COUNT(*) FROM public.notifications 
     WHERE entity_type = 'tablos' 
     AND entity_id = current_setting('test.tablo_id')
     AND user_id = current_setting('test.user2_id')::uuid
     AND action_type = 'updated') > 0,
    'Notification should be created for collaborator when tablo is updated'
);

-- ============================================================================
-- Task Trigger Tests
-- ============================================================================

-- Update task status
DO $$
BEGIN
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
    UPDATE public.tasks 
    SET status = 'in_progress'
    WHERE id = current_setting('test.task_id');
END $$;

SELECT ok(
    (SELECT COUNT(*) FROM public.notifications 
     WHERE entity_type = 'tasks' 
     AND entity_id = current_setting('test.task_id')
     AND action_type = 'updated') > 0,
    'Notification should be created when task is updated'
);

-- ============================================================================
-- Event Trigger Tests
-- ============================================================================

-- Update event
DO $$
BEGIN
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
    UPDATE public.events 
    SET title = 'Updated Test Event'
    WHERE id = current_setting('test.event_id');
END $$;

SELECT ok(
    (SELECT COUNT(*) FROM public.notifications 
     WHERE entity_type = 'events' 
     AND entity_id = current_setting('test.event_id')
     AND action_type = 'updated') > 0,
    'Notification should be created when event is updated'
);

-- ============================================================================
-- Note Trigger Tests
-- ============================================================================

-- Update note
DO $$
BEGIN
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
    UPDATE public.notes 
    SET title = 'Updated Test Note'
    WHERE id = current_setting('test.note_id');
END $$;

SELECT ok(
    (SELECT COUNT(*) FROM public.notifications 
     WHERE entity_type = 'notes' 
     AND entity_id = current_setting('test.note_id')
     AND action_type = 'updated') > 0,
    'Notification should be created when note is updated'
);

-- ============================================================================
-- Tablo Access Trigger Tests
-- ============================================================================

-- Grant access to a new user and verify notification
DO $$
DECLARE
    test_user3_id uuid := gen_random_uuid();
BEGIN
    -- Insert test user3
    INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
    VALUES 
        (test_user3_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notiftest3_' || test_user3_id::text || '@test.com', 'encrypted', now(), now(), now())
    ON CONFLICT DO NOTHING;
    
    INSERT INTO public.profiles (id, email, first_name, last_name, short_user_id)
    VALUES 
        (test_user3_id, 'notiftest3_' || test_user3_id::text || '@test.com', 'NotifTest', 'User3', substring(test_user3_id::text from 1 for 8))
    ON CONFLICT DO NOTHING;
    
    PERFORM set_config('test.user3_id', test_user3_id::text, true);
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
    
    -- Grant access
    INSERT INTO public.tablo_access (tablo_id, user_id, granted_by, is_active, is_admin)
    VALUES (current_setting('test.tablo_id'), test_user3_id, current_setting('test.user1_id')::uuid, true, false);
END $$;

SELECT ok(
    (SELECT COUNT(*) FROM public.notifications 
     WHERE entity_type = 'tablo_access' 
     AND user_id = current_setting('test.user3_id')::uuid
     AND action_type = 'created') > 0,
    'Notification should be created when user is granted tablo access'
);

-- ============================================================================
-- Tablo Invite Trigger Tests
-- ============================================================================

-- Create an invite
DO $$
BEGIN
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
    
    INSERT INTO public.tablo_invites (tablo_id, invited_email, invited_by, invite_token, is_pending)
    VALUES (
        current_setting('test.tablo_id'), 
        'notiftest2_' || current_setting('test.user2_id') || '@test.com',
        current_setting('test.user1_id')::uuid,
        'test-token-' || gen_random_uuid()::text,
        true
    );
END $$;

SELECT ok(
    (SELECT COUNT(*) FROM public.notifications 
     WHERE entity_type = 'tablo_invites' 
     AND user_id = current_setting('test.user2_id')::uuid) > 0,
    'Notification should be created when existing user is invited'
);

-- Test: No notification for non-existent user
DO $$
DECLARE
    count_before bigint;
    count_after bigint;
BEGIN
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
    
    SELECT COUNT(*) INTO count_before FROM public.notifications;
    
    -- Invite a non-existent user
    INSERT INTO public.tablo_invites (tablo_id, invited_email, invited_by, invite_token, is_pending)
    VALUES (
        current_setting('test.tablo_id'), 
        'nonexistent@example.com',
        current_setting('test.user1_id')::uuid,
        'test-token-' || gen_random_uuid()::text,
        true
    );
    
    SELECT COUNT(*) INTO count_after FROM public.notifications;
    
    PERFORM set_config('test.nonexistent_notif_diff', (count_after - count_before)::text, true);
END $$;

SELECT is(
    current_setting('test.nonexistent_notif_diff', true)::integer,
    0,
    'No notification should be created when inviting non-existent user'
);

-- Test: Case-insensitive email matching
DO $$
DECLARE
    count_before bigint;
    count_after bigint;
BEGIN
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
    
    -- Count notifications for user2 before
    SELECT COUNT(*) INTO count_before 
    FROM public.notifications 
    WHERE entity_type = 'tablo_invites' 
    AND user_id = current_setting('test.user2_id')::uuid;
    
    -- Invite with uppercase email to test case-insensitivity
    INSERT INTO public.tablo_invites (tablo_id, invited_email, invited_by, invite_token, is_pending)
    VALUES (
        current_setting('test.tablo_id'), 
        UPPER('notiftest2_' || current_setting('test.user2_id') || '@test.com'),
        current_setting('test.user1_id')::uuid,
        'test-token-uppercase-' || gen_random_uuid()::text,
        true
    );
    
    -- Count after
    SELECT COUNT(*) INTO count_after 
    FROM public.notifications 
    WHERE entity_type = 'tablo_invites' 
    AND user_id = current_setting('test.user2_id')::uuid;
    
    PERFORM set_config('test.case_insensitive_notif_diff', (count_after - count_before)::text, true);
END $$;

SELECT is(
    current_setting('test.case_insensitive_notif_diff', true)::integer,
    1,
    'Notification should be created with case-insensitive email matching'
);

-- ============================================================================
-- Multiple Notifications Tests (Exact Count Verification)
-- ============================================================================

-- Setup: Create additional users with access to test tablo
DO $$
DECLARE
    test_user4_id uuid := gen_random_uuid();
    test_user5_id uuid := gen_random_uuid();
BEGIN
    -- Insert test users 4 and 5
    INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
    VALUES 
        (test_user4_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notiftest4_' || test_user4_id::text || '@test.com', 'encrypted', now(), now(), now()),
        (test_user5_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notiftest5_' || test_user5_id::text || '@test.com', 'encrypted', now(), now(), now())
    ON CONFLICT DO NOTHING;
    
    INSERT INTO public.profiles (id, email, first_name, last_name, short_user_id)
    VALUES 
        (test_user4_id, 'notiftest4_' || test_user4_id::text || '@test.com', 'NotifTest', 'User4', substring(test_user4_id::text from 1 for 8)),
        (test_user5_id, 'notiftest5_' || test_user5_id::text || '@test.com', 'NotifTest', 'User5', substring(test_user5_id::text from 1 for 8))
    ON CONFLICT DO NOTHING;
    
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
    
    -- Grant access to users 4 and 5 on the test tablo
    INSERT INTO public.tablo_access (tablo_id, user_id, granted_by, is_active, is_admin)
    VALUES 
        (current_setting('test.tablo_id'), test_user4_id, current_setting('test.user1_id')::uuid, true, false),
        (current_setting('test.tablo_id'), test_user5_id, current_setting('test.user1_id')::uuid, true, false);
    
    PERFORM set_config('test.user4_id', test_user4_id::text, true);
    PERFORM set_config('test.user5_id', test_user5_id::text, true);
END $$;

-- Test: Multiple users notified when tablo is updated
-- Expected: user2, user3, user4, user5 should be notified (4 notifications), but NOT user1 (the actor)
DO $$
DECLARE
    notif_count_before bigint;
    notif_count_after bigint;
BEGIN
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
    
    -- Count notifications before update
    SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
    
    -- Update tablo (user1 is the actor)
    UPDATE public.tablos 
    SET name = 'Tablo Updated for Multiple Users Test'
    WHERE id = current_setting('test.tablo_id');
    
    -- Count notifications after update
    SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
    
    PERFORM set_config('test.multi_notif_count', (notif_count_after - notif_count_before)::text, true);
END $$;

SELECT is(
    current_setting('test.multi_notif_count', true)::integer,
    4,
    'Exactly 4 notifications should be created when tablo with 4 collaborators (excluding actor) is updated'
);

-- Test: Verify each specific user received the notification
SELECT is(
    (SELECT COUNT(*)::integer FROM public.notifications 
     WHERE entity_type = 'tablos' 
     AND entity_id = current_setting('test.tablo_id')
     AND message->>'en' = 'Tablo "Tablo Updated for Multiple Users Test" was updated'
     AND user_id = current_setting('test.user2_id')::uuid),
    1,
    'User2 should receive exactly 1 notification for tablo update'
);

SELECT is(
    (SELECT COUNT(*)::integer FROM public.notifications 
     WHERE entity_type = 'tablos' 
     AND entity_id = current_setting('test.tablo_id')
     AND message->>'en' = 'Tablo "Tablo Updated for Multiple Users Test" was updated'
     AND user_id = current_setting('test.user4_id')::uuid),
    1,
    'User4 should receive exactly 1 notification for tablo update'
);

SELECT is(
    (SELECT COUNT(*)::integer FROM public.notifications 
     WHERE entity_type = 'tablos' 
     AND entity_id = current_setting('test.tablo_id')
     AND message->>'en' = 'Tablo "Tablo Updated for Multiple Users Test" was updated'
     AND user_id = current_setting('test.user5_id')::uuid),
    1,
    'User5 should receive exactly 1 notification for tablo update'
);

SELECT is(
    (SELECT COUNT(*)::integer FROM public.notifications 
     WHERE entity_type = 'tablos' 
     AND entity_id = current_setting('test.tablo_id')
     AND message->>'en' = 'Tablo "Tablo Updated for Multiple Users Test" was updated'
     AND user_id = current_setting('test.user3_id')::uuid),
    1,
    'User3 should receive exactly 1 notification for tablo update'
);

-- Test: Actor (user1) should NOT receive a notification
SELECT is(
    (SELECT COUNT(*)::integer FROM public.notifications 
     WHERE entity_type = 'tablos' 
     AND entity_id = current_setting('test.tablo_id')
     AND message->>'en' = 'Tablo "Tablo Updated for Multiple Users Test" was updated'
     AND actor_id = current_setting('test.user1_id')::uuid
     AND user_id = current_setting('test.user1_id')::uuid),
    0,
    'Actor should not receive notification for their own action'
);

-- Test: Multiple users notified when new task is created
-- Expected: user1 (owner), user2, user4, user5 should be notified, but NOT user3 (who created the task)
DO $$
DECLARE
    notif_count_before bigint;
    notif_count_after bigint;
    new_task_id text;
BEGIN
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user3_id'))::text, true);
    
    -- Count notifications before creating task
    SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
    
    -- User3 creates a new task
    INSERT INTO public.tasks (tablo_id, title, description, status, position)
    VALUES (current_setting('test.tablo_id'), 'Multi-User Test Task', 'Task to test multiple notifications', 'todo', 1)
    RETURNING id INTO new_task_id;
    
    -- Count notifications after creating task
    SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
    
    PERFORM set_config('test.multi_task_notif_count', (notif_count_after - notif_count_before)::text, true);
    PERFORM set_config('test.multi_task_id', new_task_id, true);
END $$;

SELECT is(
    current_setting('test.multi_task_notif_count', true)::integer,
    4,
    'Exactly 4 notifications should be created when task is added to tablo with 4 collaborators (owner + 3 others, excluding actor)'
);

-- Test: Multiple users notified when task is assigned
-- Expected: Assignee (user5) + owner (user1) + collaborators (user2, user4) should be notified, but NOT user3 (the actor)
DO $$
DECLARE
    notif_count_before bigint;
    notif_count_after bigint;
BEGIN
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user3_id'))::text, true);
    
    -- Count notifications before update
    SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
    
    -- User3 assigns the task to user5 (without changing status to avoid message priority issues)
    UPDATE public.tasks 
    SET assignee_id = current_setting('test.user5_id')::uuid
    WHERE id = current_setting('test.multi_task_id');
    
    -- Count notifications after update
    SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
    
    PERFORM set_config('test.task_assign_notif_count', (notif_count_after - notif_count_before)::text, true);
END $$;

SELECT is(
    current_setting('test.task_assign_notif_count', true)::integer,
    4,
    'Exactly 4 notifications should be created when task is assigned (owner + assignee + 2 other collaborators, excluding actor)'
);

-- Test: Assignee (user5) receives notification about being assigned
SELECT is(
    (SELECT COUNT(*)::integer FROM public.notifications 
     WHERE entity_type = 'tasks' 
     AND entity_id = current_setting('test.multi_task_id')
     AND user_id = current_setting('test.user5_id')::uuid
     AND message->>'en' LIKE '%assigned%'),
    1,
    'Assignee should receive exactly 1 notification about task assignment'
);

-- ============================================================================
-- Metadata and Message Tests
-- ============================================================================

-- Test: Notification metadata should be valid JSONB
SELECT ok(
    (SELECT COUNT(*) FROM public.notifications 
     WHERE jsonb_typeof(metadata) = 'object') > 0,
    'Notification metadata should be valid JSONB objects'
);

-- Test: Messages should be non-empty and have both en and fr translations
SELECT ok(
    (SELECT COUNT(*) FROM public.notifications 
     WHERE length(message->>'en') = 0 
        OR length(message->>'fr') = 0
        OR message->>'en' IS NULL
        OR message->>'fr' IS NULL) = 0,
    'All notification messages should have non-empty en and fr translations'
);

-- Test: Action types should be valid
SELECT ok(
    (SELECT COUNT(*) FROM public.notifications 
     WHERE action_type NOT IN ('created', 'updated')) = 0,
    'All notifications should have valid action_type (created or updated)'
);

-- ============================================================================
-- Edge Cases
-- ============================================================================

-- Test: Soft delete should not create notification
DO $$
DECLARE
    soft_delete_tablo_id text;
    count_before bigint;
    count_after bigint;
BEGIN
    PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
    
    -- Create a tablo and immediately soft delete it
    INSERT INTO public.tablos (owner_id, name, status, position)
    VALUES (current_setting('test.user1_id')::uuid, 'Soft Delete Test', 'todo', 0)
    RETURNING id INTO soft_delete_tablo_id;
    
    -- Count notifications before soft delete
    SELECT COUNT(*) INTO count_before 
    FROM public.notifications 
    WHERE entity_id = soft_delete_tablo_id;
    
    -- Soft delete
    UPDATE public.tablos 
    SET deleted_at = now() 
    WHERE id = soft_delete_tablo_id;
    
    -- Count notifications after soft delete
    SELECT COUNT(*) INTO count_after
    FROM public.notifications 
    WHERE entity_id = soft_delete_tablo_id;
    
    -- Store results
    PERFORM set_config('test.notif_count_before', count_before::text, true);
    PERFORM set_config('test.notif_count_after', count_after::text, true);
END $$;

SELECT is(
    current_setting('test.notif_count_before', true),
    current_setting('test.notif_count_after', true),
    'Soft delete should not create additional notifications'
);

-- ============================================================================
-- NULL Actor Tests (Service Role / API Operations)
-- ============================================================================

-- Test: Tablo update with NULL actor should notify all collaborators
DO $$
DECLARE
    notif_count_before bigint;
    notif_count_after bigint;
BEGIN
    -- Clear auth context (simulates service role call with no auth.uid())
    PERFORM set_config('request.jwt.claims', NULL, true);
    
    -- Count notifications before update
    SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
    
    -- Update tablo without actor context
    UPDATE public.tablos 
    SET name = 'Updated by Service Role'
    WHERE id = current_setting('test.tablo_id');
    
    -- Count notifications after update
    SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
    
    PERFORM set_config('test.null_actor_notif_count', (notif_count_after - notif_count_before)::text, true);
END $$;

SELECT is(
    current_setting('test.null_actor_notif_count', true)::integer,
    5,
    'With NULL actor, all collaborators (including owner) should be notified on tablo update (5 total: user1-5)'
);

-- Test: Task creation with NULL actor
DO $$
DECLARE
    notif_count_before bigint;
    notif_count_after bigint;
    new_task_id text;
BEGIN
    PERFORM set_config('request.jwt.claims', NULL, true);
    
    SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
    
    -- Create task without actor context
    INSERT INTO public.tasks (tablo_id, title, description, status, position)
    VALUES (current_setting('test.tablo_id'), 'Service Role Task', 'Created via service role', 'todo', 2)
    RETURNING id INTO new_task_id;
    
    SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
    
    PERFORM set_config('test.null_actor_task_notif_count', (notif_count_after - notif_count_before)::text, true);
    PERFORM set_config('test.null_actor_task_id', new_task_id, true);
END $$;

SELECT is(
    current_setting('test.null_actor_task_notif_count', true)::integer,
    5,
    'With NULL actor, all tablo members should be notified on task creation (owner + 4 collaborators)'
);

-- Test: Tablo access grant with NULL actor
DO $$
DECLARE
    test_user6_id uuid := gen_random_uuid();
    notif_count_before bigint;
    notif_count_after bigint;
BEGIN
    -- Insert test user6
    INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
    VALUES 
        (test_user6_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notiftest6_' || test_user6_id::text || '@test.com', 'encrypted', now(), now(), now())
    ON CONFLICT DO NOTHING;
    
    INSERT INTO public.profiles (id, email, first_name, last_name, short_user_id)
    VALUES 
        (test_user6_id, 'notiftest6_' || test_user6_id::text || '@test.com', 'NotifTest', 'User6', substring(test_user6_id::text from 1 for 8))
    ON CONFLICT DO NOTHING;
    
    PERFORM set_config('test.user6_id', test_user6_id::text, true);
    PERFORM set_config('request.jwt.claims', NULL, true);
    
    SELECT COUNT(*) INTO notif_count_before 
    FROM public.notifications 
    WHERE user_id = test_user6_id;
    
    -- Grant access without actor context
    INSERT INTO public.tablo_access (tablo_id, user_id, granted_by, is_active, is_admin)
    VALUES (current_setting('test.tablo_id'), test_user6_id, current_setting('test.user1_id')::uuid, true, false);
    
    SELECT COUNT(*) INTO notif_count_after 
    FROM public.notifications 
    WHERE user_id = test_user6_id;
    
    PERFORM set_config('test.null_actor_access_notif_diff', (notif_count_after - notif_count_before)::text, true);
END $$;

SELECT is(
    current_setting('test.null_actor_access_notif_diff', true)::integer,
    1,
    'With NULL actor, user should still be notified when granted tablo access'
);

-- Test: Tablo invite with NULL actor (simulating API invite)
DO $$
DECLARE
    notif_count_before bigint;
    notif_count_after bigint;
BEGIN
    PERFORM set_config('request.jwt.claims', NULL, true);
    
    -- Count notifications for user6 before invite
    SELECT COUNT(*) INTO notif_count_before 
    FROM public.notifications 
    WHERE user_id = current_setting('test.user6_id')::uuid
    AND entity_type = 'tablo_invites';
    
    -- Invite user6 without actor context (via service role)
    INSERT INTO public.tablo_invites (tablo_id, invited_email, invited_by, invite_token, is_pending)
    VALUES (
        current_setting('test.tablo_id'), 
        'notiftest6_' || current_setting('test.user6_id') || '@test.com',
        current_setting('test.user1_id')::uuid,
        'test-token-null-actor-' || gen_random_uuid()::text,
        true
    );
    
    -- Count after
    SELECT COUNT(*) INTO notif_count_after 
    FROM public.notifications 
    WHERE user_id = current_setting('test.user6_id')::uuid
    AND entity_type = 'tablo_invites';
    
    PERFORM set_config('test.null_actor_invite_notif_diff', (notif_count_after - notif_count_before)::text, true);
END $$;

SELECT is(
    current_setting('test.null_actor_invite_notif_diff', true)::integer,
    1,
    'With NULL actor, invited user should still receive notification (API-initiated invite)'
);

-- Test: Event creation with NULL actor
DO $$
DECLARE
    notif_count_before bigint;
    notif_count_after bigint;
    new_event_id text;
BEGIN
    PERFORM set_config('request.jwt.claims', NULL, true);
    
    SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
    
    -- Create event without actor context
    INSERT INTO public.events (tablo_id, title, description, start_date, start_time, created_by)
    VALUES (current_setting('test.tablo_id'), 'Service Role Event', 'Event via service role', CURRENT_DATE, '14:00', current_setting('test.user1_id')::uuid)
    RETURNING id INTO new_event_id;
    
    SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
    
    PERFORM set_config('test.null_actor_event_notif_count', (notif_count_after - notif_count_before)::text, true);
END $$;

SELECT is(
    current_setting('test.null_actor_event_notif_count', true)::integer,
    6,
    'With NULL actor, all tablo members should be notified on event creation (owner + 5 collaborators including user6)'
);

-- Test: Note update with NULL actor
DO $$
DECLARE
    notif_count_before bigint;
    notif_count_after bigint;
BEGIN
    PERFORM set_config('request.jwt.claims', NULL, true);
    
    SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
    
    -- Update note without actor context
    UPDATE public.notes 
    SET content = 'Updated by service role'
    WHERE id = current_setting('test.note_id');
    
    SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
    
    PERFORM set_config('test.null_actor_note_notif_count', (notif_count_after - notif_count_before)::text, true);
END $$;

SELECT is(
    current_setting('test.null_actor_note_notif_count', true)::integer,
    2,
    'With NULL actor, note owner (user1) and collaborator (user2) should both be notified on note update'
);

select * from finish();
rollback;
