/**
 * ICD Test Runner — CRUD: Tasks
 * Personal task lifecycle, validation (missing name, invalid cron,
 * workflow type), webhook-triggered tasks, webhook secret auto-gen,
 * admin task operations.
 */
(function () {
  'use strict';
  var T = window.ICD;
  if (!T) return;
  if (!T.crud) T.crud = {};

  T.crud.tasks = async function (testTag) {

    // ── Personal Tasks CRUD ──
    var taskId = null;
    var taskTriggerToken = null;

    await T.test('crud', 'tasks', 'POST /tasks (create prompt task)', async function () {
      var d = await T.apiPost('/tasks', {
        name: testTag + '-task',
        description: 'ICD integration test task',
        task_type: 'prompt',
        schedule: '@daily',
        user_prompt: 'Summarize test data',
        model_id: 'test-model',
        timezone: 'UTC'
      });
      T.assertShape(d, T.S.taskFull, 'created task');
      T.assert(d.task_type === 'prompt', 'task_type should be prompt');
      T.assert(d.scope === 'personal', 'default scope should be personal');
      T.assert(d.is_active === true, 'new task should be active');
      T.assert(d.next_run_at !== null && d.next_run_at !== undefined, 'cron task should have next_run_at');
      T.assert(d.max_tokens > 0, 'budget defaults should be applied');
      taskId = d.id;
      T.registerCleanup(function () { if (taskId) return T.safeDelete('/tasks/' + taskId); });
    });

    if (taskId) {
      await T.test('crud', 'tasks', 'GET /tasks/:id (read)', async function () {
        var d = await T.apiGet('/tasks/' + taskId);
        T.assertShape(d, T.S.taskFull, 'task');
        T.assert(d.id === taskId, 'id mismatch');
        T.assert(d.name.indexOf(testTag) !== -1, 'name mismatch');
      });

      await T.test('crud', 'tasks', 'PUT /tasks/:id (update)', async function () {
        var d = await T.apiPut('/tasks/' + taskId, {
          name: testTag + '-task-updated',
          user_prompt: 'Updated prompt'
        });
        T.assert(d.name === testTag + '-task-updated', 'name not updated');
      });

      await T.test('crud', 'tasks', 'GET /tasks (list mine)', async function () {
        var d = await T.apiGet('/tasks');
        T.assertHasKey(d, 'data', '/tasks');
        T.assert(Array.isArray(d.data), 'data should be array');
        T.assert(d.data.length >= 1, 'should have at least 1 task');
        var found = d.data.some(function (t) { return t.id === taskId; });
        T.assert(found, 'created task should be in list');
      });

      await T.test('crud', 'tasks', 'GET /tasks/:id/runs (list runs)', async function () {
        var d = await T.apiGet('/tasks/' + taskId + '/runs');
        T.assertHasKey(d, 'data', '/runs');
        T.assert(Array.isArray(d.data), 'data should be array');
      });

      await T.test('crud', 'tasks', 'POST /tasks/:id/run (run now)', async function () {
        var d = await T.apiPost('/tasks/' + taskId + '/run', {});
        T.assertHasKey(d, 'scheduled', '/run');
        T.assert(d.scheduled === true, 'should be scheduled');
      });

      await T.test('crud', 'tasks', 'POST /tasks/:id/kill (no active run → 404)', async function () {
        var token = await T.getAuthToken();
        var d = await T.authFetch(token, 'POST', '/tasks/' + taskId + '/kill');
        // 200 = killed (if run started), 404 = no active run — both acceptable
        T.assert(d._status === 200 || d._status === 404,
          'expected 200 or 404, got ' + d._status);
      });

      await T.test('crud', 'tasks', 'PUT /tasks/:id (update schedule)', async function () {
        var d = await T.apiPut('/tasks/' + taskId, { schedule: '@hourly' });
        T.assert(d.schedule === '@hourly', 'schedule not updated');
      });

      await T.test('crud', 'tasks', 'PUT /tasks/:id (deactivate)', async function () {
        var d = await T.apiPut('/tasks/' + taskId, { is_active: false });
        T.assert(d.is_active === false, 'should be deactivated');
      });

      await T.test('crud', 'tasks', 'DELETE /tasks/:id', async function () {
        await T.safeDelete('/tasks/' + taskId);
        taskId = null;
      });
    }

    // ── Task Validation Tests ──
    await T.test('crud', 'tasks', 'POST /tasks (missing name → 400)', async function () {
      var token = await T.getAuthToken();
      var d = await T.authFetch(token, 'POST', '/tasks', {
        task_type: 'prompt', schedule: '@daily',
        user_prompt: 'x', model_id: 'm'
      });
      T.assertStatus(d, 400, 'missing name');
    });

    await T.test('crud', 'tasks', 'POST /tasks (invalid cron → 400)', async function () {
      var token = await T.getAuthToken();
      var d = await T.authFetch(token, 'POST', '/tasks', {
        name: testTag + '-bad-cron', task_type: 'prompt',
        schedule: 'not-a-cron', user_prompt: 'x', model_id: 'm'
      });
      T.assertStatus(d, 400, 'invalid cron');
    });

    await T.test('crud', 'tasks', 'POST /tasks (workflow type → 400 not implemented)', async function () {
      var token = await T.getAuthToken();
      var d = await T.authFetch(token, 'POST', '/tasks', {
        name: testTag + '-wf', task_type: 'workflow',
        schedule: '@daily', workflow_id: '00000000-0000-0000-0000-000000000001',
        model_id: 'm'
      });
      T.assertStatus(d, 400, 'workflow type rejected');
    });

    // ── Webhook-Triggered Task + Trigger Endpoint ──
    var webhookTaskId = null;
    var webhookTriggerToken = null;

    await T.test('crud', 'tasks', 'POST /tasks (create webhook task)', async function () {
      var d = await T.apiPost('/tasks', {
        name: testTag + '-webhook-task',
        task_type: 'prompt',
        schedule: 'webhook',
        user_prompt: 'Process trigger data',
        model_id: 'test-model'
      });
      T.assertShape(d, T.S.taskFull, 'webhook task');
      T.assert(d.schedule === 'webhook', 'schedule should be webhook');
      T.assert(d.trigger_token && d.trigger_token.length > 0, 'webhook task should have trigger_token');
      T.assert(d.next_run_at === null || d.next_run_at === undefined, 'webhook task should have no next_run_at');
      webhookTaskId = d.id;
      webhookTriggerToken = d.trigger_token;
      T.registerCleanup(function () { if (webhookTaskId) return T.safeDelete('/tasks/' + webhookTaskId); });
    });

    if (webhookTaskId && webhookTriggerToken) {
      await T.test('crud', 'tasks', 'POST /hooks/t/:token (fire trigger → 202)', async function () {
        var d = await T.publicPost('/hooks/t/' + webhookTriggerToken, {
          build_id: 12345, status: 'failed', repo: 'icd-test'
        });
        T.assertStatus(d, 202, 'trigger');
        T.assert(d.triggered === true, 'should be triggered');
        T.assert(d.run_id && d.run_id.length > 0, 'should return run_id');
        T.assert(d.task_id === webhookTaskId, 'task_id should match');
      });

      await T.test('crud', 'tasks', 'POST /hooks/t/:token (duplicate → 409)', async function () {
        var d = await T.publicPost('/hooks/t/' + webhookTriggerToken, { test: true });
        T.assertStatus(d, 409, 'duplicate trigger');
      });

      await T.test('crud', 'tasks', 'POST /hooks/t/bad-token (invalid → 404)', async function () {
        var d = await T.publicPost('/hooks/t/nonexistent-token-value', { test: true });
        T.assertStatus(d, 404, 'bad token');
      });

      // Deactivate then trigger → 410
      await T.test('crud', 'tasks', 'PUT+POST deactivated webhook task → 410', async function () {
        await T.apiPut('/tasks/' + webhookTaskId, { is_active: false });
        var d = await T.publicPost('/hooks/t/' + webhookTriggerToken, { test: true });
        T.assertStatus(d, 410, 'inactive trigger');
        // Re-activate for cleanup
        await T.apiPut('/tasks/' + webhookTaskId, { is_active: true });
      });

      await T.test('crud', 'tasks', 'GET /tasks/:id/runs (webhook task has queued run)', async function () {
        var d = await T.apiGet('/tasks/' + webhookTaskId + '/runs');
        T.assertHasKey(d, 'data', '/runs');
        T.assert(Array.isArray(d.data), 'data should be array');
        T.assert(d.data.length >= 1, 'webhook task should have at least 1 run from trigger');
        T.assertShape(d.data[0], T.S.taskRun, 'run[0]');
      });

      await T.test('crud', 'tasks', 'DELETE /tasks/:id (webhook task)', async function () {
        await T.safeDelete('/tasks/' + webhookTaskId);
        webhookTaskId = null;
      });
    }

    // ── Webhook Secret Auto-Generation ──
    await T.test('crud', 'tasks', 'POST /tasks (webhook_url → auto webhook_secret)', async function () {
      var d = await T.apiPost('/tasks', {
        name: testTag + '-secret-task',
        task_type: 'prompt', schedule: '@daily',
        user_prompt: 'x', model_id: 'm',
        webhook_url: 'https://example.com/hook'
      });
      T.assert(d.webhook_secret && d.webhook_secret.length > 0,
        'webhook_secret should be auto-generated when webhook_url is set');
      if (d.id) await T.safeDelete('/tasks/' + d.id);
    });

    // ── Admin Task Operations ──
    if (T.user.role === 'admin') {
      var adminTaskId = null;

      await T.test('crud', 'tasks', 'POST /tasks (create for admin ops)', async function () {
        var d = await T.apiPost('/tasks', {
          name: testTag + '-admin-task',
          task_type: 'prompt', schedule: '@daily',
          user_prompt: 'admin test', model_id: 'm'
        });
        adminTaskId = d.id;
        T.registerCleanup(function () { if (adminTaskId) return T.safeDelete('/tasks/' + adminTaskId); });
      });

      await T.test('crud', 'tasks', 'GET /admin/tasks (list all)', async function () {
        var d = await T.apiGet('/admin/tasks');
        T.assertHasKey(d, 'data', '/admin/tasks');
        T.assert(Array.isArray(d.data), 'data should be array');
        if (adminTaskId) {
          var found = d.data.some(function (t) { return t.id === adminTaskId; });
          T.assert(found, 'admin-created task should appear in admin list');
        }
      });

      if (adminTaskId) {
        await T.test('crud', 'tasks', 'POST /admin/tasks/:id/run (admin run)', async function () {
          var d = await T.apiPost('/admin/tasks/' + adminTaskId + '/run', {});
          T.assertHasKey(d, 'scheduled', '/admin/run');
          T.assert(d.scheduled === true, 'should be scheduled');
        });

        await T.test('crud', 'tasks', 'POST /admin/tasks/:id/kill (admin kill)', async function () {
          var token = await T.getAuthToken();
          var d = await T.authFetch(token, 'POST', '/admin/tasks/' + adminTaskId + '/kill');
          T.assert(d._status === 200 || d._status === 404,
            'expected 200 or 404, got ' + d._status);
        });

        await T.test('crud', 'tasks', 'DELETE /admin/tasks/:id (admin delete)', async function () {
          await T.safeDelete('/admin/tasks/' + adminTaskId);
          adminTaskId = null;
        });
      }
    }

    // ── System functions (v0.28.6) ──

    await T.test('crud', 'tasks', 'GET /admin/system-functions', async function () {
      var d = await T.apiGet('/admin/system-functions');
      T.assertHasKey(d, 'data', 'system-functions');
      T.assert(Array.isArray(d.data), 'data should be array');
      T.assert(d.data.length >= 4, 'should have at least 4 built-in functions, got ' + d.data.length);
      var names = d.data.map(function(f) { return f.name; });
      T.assert(names.indexOf('session_cleanup') >= 0, 'should include session_cleanup');
      T.assert(names.indexOf('staleness_check') >= 0, 'should include staleness_check');
      T.assert(names.indexOf('retention_sweep') >= 0, 'should include retention_sweep');
      T.assert(names.indexOf('health_prune') >= 0, 'should include health_prune');
      // Verify shape
      T.assert(typeof d.data[0].name === 'string', 'name should be string');
      T.assert(typeof d.data[0].description === 'string', 'description should be string');
    });

    var sysTaskId = null;
    await T.test('crud', 'tasks', 'POST /tasks (create system task)', async function () {
      var d = await T.apiPost('/tasks', {
        name: testTag + '-system-task',
        task_type: 'system',
        system_function: 'health_prune',
        schedule: '0 3 * * *',
        scope: 'global',
      });
      T.assert(d.id, 'should return id');
      T.assert(d.task_type === 'system', 'task_type should be system');
      T.assert(d.system_function === 'health_prune', 'system_function should be health_prune');
      sysTaskId = d.id;
      T.registerCleanup(function () { if (sysTaskId) return T.safeDelete('/tasks/' + sysTaskId); });
    });

    if (sysTaskId) {
      await T.test('crud', 'tasks', 'DELETE /tasks/:id (cleanup system task)', async function () {
        await T.safeDelete('/tasks/' + sysTaskId);
        sysTaskId = null;
      });
    }

    await T.test('crud', 'tasks', 'POST /tasks (system task — invalid function → 400)', async function () {
      var token = await T.getAuthToken();
      var d = await T.authFetch(token, 'POST', '/tasks', {
        name: 'bad-func',
        task_type: 'system',
        system_function: 'nonexistent_func',
        schedule: '0 3 * * *',
      });
      T.assertStatus(d, 400, 'invalid system function');
    });

    await T.test('crud', 'tasks', 'POST /tasks (system task — missing function → 400)', async function () {
      var token = await T.getAuthToken();
      var d = await T.authFetch(token, 'POST', '/tasks', {
        name: 'no-func',
        task_type: 'system',
        schedule: '0 3 * * *',
      });
      T.assertStatus(d, 400, 'missing system_function');
    });

  };
})();
