/**
 * ICD Test Runner — CRUD: Workspaces
 * Workspace lifecycle — create, read, update, file ops, stats, isolation, delete.
 * Git credentials list envelope.
 */
(function () {
  'use strict';
  var T = window.ICD;
  if (!T) return;
  if (!T.crud) T.crud = {};

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

    // ── GET /workspaces — list envelope ──
    await T.test('crud', 'workspaces', 'GET /workspaces (list envelope)', async function () {
      var d = await T.apiGet('/workspaces');
      T.assertHasKey(d, 'data', 'GET /workspaces');
      T.assert(Array.isArray(d.data), 'data must be array');
    });

    // ── GET /git-credentials — list envelope ──
    await T.test('crud', 'workspaces', 'GET /git-credentials (list envelope)', async function () {
      var d = await T.apiGet('/git-credentials');
      T.assertHasKey(d, 'data', 'GET /git-credentials');
      T.assert(Array.isArray(d.data), 'data must be array');
    });

    // ── POST /git-credentials/generate — server-side keygen (v0.28.6) ──
    var generatedKeyId = null;
    await T.test('crud', 'workspaces', 'POST /git-credentials/generate', async function () {
      var d = await T.apiPost('/git-credentials/generate', { name: 'ICD Test Key' });
      T.assert(d.id, 'should return id');
      T.assert(d.auth_type === 'ssh_key', 'auth_type should be ssh_key');
      T.assert(d.public_key && d.public_key.length > 0, 'should return public_key');
      T.assert(d.fingerprint && d.fingerprint.startsWith('SHA256:'), 'should return SHA256 fingerprint');
      T.assert(!d.encrypted_data, 'must NOT expose encrypted_data');
      T.assert(!d.nonce, 'must NOT expose nonce');
      generatedKeyId = d.id;
      T.registerCleanup(function () { if (generatedKeyId) return T.safeDelete('/git-credentials/' + generatedKeyId); });
    });

    if (generatedKeyId) {
      await T.test('crud', 'workspaces', 'GET /git-credentials/:id/public-key', async function () {
        var d = await T.apiGet('/git-credentials/' + generatedKeyId + '/public-key');
        T.assertHasKey(d, 'public_key', 'public-key response');
        T.assertHasKey(d, 'fingerprint', 'public-key response');
        T.assert(d.public_key.length > 0, 'public_key should be non-empty');
      });

      await T.test('crud', 'workspaces', 'GET /git-credentials (list after generate)', async function () {
        var d = await T.apiGet('/git-credentials');
        var found = (d.data || []).find(function (c) { return c.id === generatedKeyId; });
        T.assert(found, 'generated key should appear in list');
        T.assert(found.public_key, 'list item should have public_key');
        T.assert(found.fingerprint, 'list item should have fingerprint');
      });

      await T.test('crud', 'workspaces', 'DELETE /git-credentials/:id', async function () {
        var d = await T.apiDelete('/git-credentials/' + generatedKeyId);
        T.assert(d.deleted === true, 'should return deleted: true');
        generatedKeyId = null;
      });
    }

    await T.test('crud', 'workspaces', 'POST /git-credentials/generate (missing name → 400)', async function () {
      var token = await T.getAuthToken();
      var d = await T.authFetch(token, 'POST', '/git-credentials/generate', {});
      T.assertStatus(d, 400, 'missing name');
    });

    // ── Workspace CRUD ──
    var wsId = null;
    await T.test('crud', 'workspaces', 'POST /workspaces (create)', async function () {
      var d = await T.apiPost('/workspaces', {
        name: testTag + '-ws',
        owner_type: 'user',
        owner_id: T.user.id
      });
      T.assertShape(d, T.S.workspace, 'workspace');
      T.assert(d.owner_id === T.user.id, 'owner_id mismatch');
      T.assert(d.status === 'active', 'status should be active');
      wsId = d.id;
      T.registerCleanup(function () { if (wsId) return T.safeDelete('/workspaces/' + wsId); });
    });

    if (wsId) {
      await T.test('crud', 'workspaces', 'GET /workspaces/:id (read)', async function () {
        var d = await T.apiGet('/workspaces/' + wsId);
        T.assertShape(d, T.S.workspace, 'workspace');
        T.assert(d.id === wsId, 'id mismatch');
        T.assert(d.name === testTag + '-ws', 'name mismatch');
      });

      // ── PATCH /workspaces/:id ──
      await T.test('crud', 'workspaces', 'PATCH /workspaces/:id (update name)', async function () {
        var d = await T.apiPatch('/workspaces/' + wsId, { name: testTag + '-ws-updated' });
        T.assertShape(d, T.S.workspace, 'workspace-patched');
        T.assert(d.name === testTag + '-ws-updated', 'name not updated');
      });

      // ── root_path must never be exposed ──
      await T.test('crud', 'workspaces', 'GET /workspaces/:id (no root_path)', async function () {
        var d = await T.apiGet('/workspaces/' + wsId);
        T.assert(d.root_path === undefined, 'root_path must not be exposed in API');
      });

      // ── File operations ──
      await T.test('crud', 'workspaces', 'GET /workspaces/:id/files (list root)', async function () {
        var d = await T.apiGet('/workspaces/' + wsId + '/files?path=/');
        T.assertHasKey(d, 'data', 'ListFiles envelope');
        T.assert(Array.isArray(d.data), 'files should be array');
      });

      // mkdir
      await T.test('crud', 'workspaces', 'POST /workspaces/:id/files/mkdir', async function () {
        var d = await T.apiPost('/workspaces/' + wsId + '/files/mkdir?path=/testdir', {});
        T.assert(d.ok === true, 'mkdir should return ok:true');
        T.assert(d.path === '/testdir', 'path mismatch');
      });

      // Write a file then read it back
      await T.test('crud', 'workspaces', 'PUT /workspaces/:id/files/write', async function () {
        var d = await T.apiPut('/workspaces/' + wsId + '/files/write?path=' + encodeURIComponent('/test.txt'), {
          content: 'Hello from ICD test runner'
        });
        T.assert(typeof d === 'object', 'expected object');
      });

      await T.test('crud', 'workspaces', 'GET /workspaces/:id/files/read', async function () {
        var d = await T.apiGet('/workspaces/' + wsId + '/files/read?path=/test.txt');
        T.assertHasKey(d, 'content', '/ws-file-read');
        T.assert(d.content === 'Hello from ICD test runner', 'content mismatch');
      });

      await T.test('crud', 'workspaces', 'GET /workspaces/:id/stats', async function () {
        var d = await T.apiGet('/workspaces/' + wsId + '/stats');
        T.assert(typeof d === 'object', 'expected object');
        T.assertHasKey(d, 'file_count', 'stats.file_count');
        T.assertHasKey(d, 'total_bytes', 'stats.total_bytes');
      });

      await T.test('crud', 'workspaces', 'GET /workspaces/:id/index-status', async function () {
        var d = await T.apiGet('/workspaces/' + wsId + '/index-status');
        T.assertHasKey(d, 'workspace_id', 'index-status.workspace_id');
        T.assertHasKey(d, 'status_counts', 'index-status.status_counts');
        T.assertHasKey(d, 'total_chunks', 'index-status.total_chunks');
      });

      // ── File cleanup ──
      await T.test('crud', 'workspaces', 'DELETE /workspaces/:id/files (file)', async function () {
        try {
          await T.safeDelete('/workspaces/' + wsId + '/files/delete?path=' + encodeURIComponent('/test.txt'));
        } catch (e) {
          if (e.message && e.message.indexOf('404') === -1) throw e;
        }
      });

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

    // ── Workspace isolation: other user cannot access ──
    // Create workspace as running admin user, try to access from fixture regular user
    if (T.fixtures.ready) {
      var fixtureUser = T.getFixtureUser('-user');
      if (fixtureUser && fixtureUser.token) {
        var isolWsId = null;

        await T.test('crud', 'workspaces', 'isolation: create admin workspace', async function () {
          var d = await T.apiPost('/workspaces', {
            name: testTag + '-iso-ws',
            owner_type: 'user',
            owner_id: T.user.id
          });
          T.assertShape(d, T.S.workspace, 'workspace-iso');
          isolWsId = d.id;
          T.registerCleanup(function () { if (isolWsId) return T.safeDelete('/workspaces/' + isolWsId); });
        });

        if (isolWsId) {
          await T.test('crud', 'workspaces', 'isolation: user cannot GET other workspace', async function () {
            var d = await T.authFetch(fixtureUser.token, 'GET', '/workspaces/' + isolWsId);
            T.assert(d._status === 403, 'expected 403, got ' + d._status);
          });

          // cleanup
          await T.test('crud', 'workspaces', 'isolation: cleanup admin workspace', async function () {
            await T.safeDelete('/workspaces/' + isolWsId);
            isolWsId = null;
          });
        }
      }
    }

  };
})();
