import assert from 'node:assert/strict';
import test from 'node:test';

import { loadServerData, saveServerData, ServerDataConflictError } from './server-data';

type MockResponse = {
  status: number;
  body: unknown;
};

test('server data saves include the latest loaded AppData version', async () => {
  const originalFetch = globalThis.fetch;
  const calls: Array<{ url: string; method: string; body?: unknown }> = [];
  const responses: MockResponse[] = [
    { status: 200, body: {} },
    {
      status: 200,
      body: { key: 'dev-tasks', value: [{ id: 'old' }], version: 'version-1' },
    },
    {
      status: 200,
      body: { key: 'dev-tasks', value: [{ id: 'next' }], version: 'version-2' },
    },
    {
      status: 200,
      body: { key: 'dev-tasks', value: [{ id: 'again' }], version: 'version-3' },
    },
    {
      status: 200,
      body: { key: 'bugs', value: [], version: 'bugs-version-1' },
    },
    {
      status: 409,
      body: {
        code: 'APP_DATA_CONFLICT',
        key: 'dev-tasks',
        currentValue: [{ id: 'server-copy' }],
        currentVersion: 'version-4',
      },
    },
  ];

  globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
    const response = responses.shift();
    if (!response) throw new Error('Unexpected fetch call');
    calls.push({
      url: String(input),
      method: init?.method ?? 'GET',
      body: init?.body ? JSON.parse(String(init.body)) : undefined,
    });
    return new Response(JSON.stringify(response.body), {
      status: response.status,
      headers: { 'Content-Type': 'application/json' },
    });
  }) as typeof fetch;

  try {
    await assert.doesNotReject(async () => {
      const loaded = await loadServerData<Array<{ id: string }>>('dev-tasks');
      assert.deepEqual(loaded, [{ id: 'old' }]);
    });

    await saveServerData('dev-tasks', [{ id: 'next' }]);
    assert.deepEqual(calls[2].body, {
      value: [{ id: 'next' }],
      version: 'version-1',
    });

    await saveServerData('dev-tasks', [{ id: 'again' }]);
    assert.deepEqual(calls[3].body, {
      value: [{ id: 'again' }],
      version: 'version-2',
    });

    await saveServerData('bugs', []);
    assert.deepEqual(calls[4].body, { value: [] });

    await assert.rejects(
      () => saveServerData('dev-tasks', [{ id: 'stale' }]),
      (error: unknown) => {
        if (!(error instanceof ServerDataConflictError)) return false;
        return error.key === 'dev-tasks' && error.currentVersion === 'version-4';
      },
    );
    assert.deepEqual(calls[5].body, {
      value: [{ id: 'stale' }],
      version: 'version-3',
    });
  } finally {
    globalThis.fetch = originalFetch;
  }
});

test('server data shares an in-flight load for the same AppData key', async () => {
  const originalFetch = globalThis.fetch;
  let dataRequests = 0;
  let releaseDataResponse!: () => void;
  const dataResponseReady = new Promise<void>((resolve) => {
    releaseDataResponse = resolve;
  });

  globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
    const url = String(input);
    if (url.endsWith('/config/ai')) {
      return new Response(JSON.stringify({}), {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
      });
    }
    if (url.endsWith('/data/test-cases')) {
      dataRequests += 1;
      await dataResponseReady;
      return new Response(JSON.stringify({
        key: 'test-cases',
        value: [{ id: 'tc-1' }],
        version: 'test-cases-version-1',
      }), {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
      });
    }
    throw new Error(`Unexpected fetch ${init?.method ?? 'GET'} ${url}`);
  }) as typeof fetch;

  try {
    const first = loadServerData<Array<{ id: string }>>('test-cases');
    const second = loadServerData<Array<{ id: string }>>('test-cases');
    releaseDataResponse();

    assert.deepEqual(await first, [{ id: 'tc-1' }]);
    assert.deepEqual(await second, [{ id: 'tc-1' }]);
    assert.equal(dataRequests, 1);
  } finally {
    globalThis.fetch = originalFetch;
  }
});

test('server data reuses a fresh cached load for repeated AppData reads', async () => {
  const originalFetch = globalThis.fetch;
  let dataRequests = 0;

  globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
    const url = String(input);
    if (url.endsWith('/config/ai')) {
      return new Response(JSON.stringify({}), {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
      });
    }
    if (url.endsWith('/data/version-plans')) {
      dataRequests += 1;
      return new Response(JSON.stringify({
        key: 'version-plans',
        value: [{ id: 'plan-1' }],
        version: 'version-plans-version-1',
      }), {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
      });
    }
    throw new Error(`Unexpected fetch ${init?.method ?? 'GET'} ${url}`);
  }) as typeof fetch;

  try {
    const first = await loadServerData<Array<{ id: string }>>('version-plans');
    const second = await loadServerData<Array<{ id: string }>>('version-plans');

    assert.deepEqual(first, [{ id: 'plan-1' }]);
    assert.deepEqual(second, [{ id: 'plan-1' }]);
    assert.equal(dataRequests, 1);
  } finally {
    globalThis.fetch = originalFetch;
  }
});
