/**
 * CosmosDatastoreProvider.updateIfMatch — verifies the If-Match (_etag) optimistic
 * concurrency mapping against a fake @azure/cosmos: a matching etag writes + bumps;
 * a stale etag surfaces as HTTP 412 -> conflict; a missing doc -> not_found.
 *
 * Uses a fake Cosmos client (no live Cosmos) that enforces accessCondition IfMatch.
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';

interface StoredRec {
  doc: Record<string, unknown>;
  etag: string;
}

const h = vi.hoisted(() => {
  const store = new Map<string, StoredRec>();
  let etagSeq = 0;
  return {
    store,
    seed(id: string, doc: Record<string, unknown>): void {
      etagSeq += 1;
      store.set(id, { doc: { ...doc, _etag: `e${etagSeq}` }, etag: `e${etagSeq}` });
    },
    nextEtag(): string {
      etagSeq += 1;
      return `e${etagSeq}`;
    },
    reset(): void {
      store.clear();
      etagSeq = 0;
    },
  };
});

vi.mock('@azure/cosmos', () => {
  type CosmosErr = Error & { code?: number };
  function err(code: number): CosmosErr {
    const e = new Error(`cosmos ${code}`) as CosmosErr;
    e.code = code;
    return e;
  }
  class CosmosClient {
    constructor(_opts: unknown) {}
    database(_name: string) {
      return {
        container: (_c: string) => ({
          item: (id: string, _pk: string) => ({
            read() {
              const rec = h.store.get(id);
              if (!rec) throw err(404);
              return Promise.resolve({ resource: { ...rec.doc, _etag: rec.etag } });
            },
            replace(
              doc: Record<string, unknown>,
              options?: { accessCondition?: { type: string; condition?: string } }
            ) {
              const rec = h.store.get(id);
              if (!rec) throw err(404);
              const cond = options?.accessCondition?.condition;
              if (cond !== undefined && cond !== rec.etag) throw err(412);
              const etag = h.nextEtag();
              const saved = { ...doc, _etag: etag };
              h.store.set(id, { doc: saved, etag });
              return Promise.resolve({ resource: saved });
            },
          }),
        }),
      };
    }
  }
  return { CosmosClient };
});

import { CosmosDatastoreProvider } from '../providers/cosmos.js';
import type { BaseDocument, DocumentCollection } from '../types.js';

interface RevDoc extends BaseDocument {
  name: string;
  rev?: number;
}

describe('CosmosDatastoreProvider.updateIfMatch', () => {
  let col: DocumentCollection<RevDoc>;
  beforeEach(() => {
    h.reset();
    const provider = new CosmosDatastoreProvider({
      endpoint: 'https://fake.documents.azure.com:443/',
      key: 'fake-key',
      database: 'test',
    });
    col = provider.getCollection<RevDoc>('rev', '/productId');
    h.seed('1', { id: '1', productId: 'p', name: 'A', rev: 0 });
  });

  it('matching etag -> writes via If-Match and returns ok', async () => {
    const cur = await col.findById('1', 'p');
    const res = await col.updateIfMatch('1', 'p', { etag: cur!._etag }, { name: 'B' });
    expect(res.ok).toBe(true);
    if (res.ok) {
      expect(res.doc.name).toBe('B');
      expect(res.doc.rev).toBe(1);
    }
  });

  it('stale etag -> Cosmos 412 -> conflict, no overwrite', async () => {
    const cur = await col.findById('1', 'p');
    const staleEtag = cur!._etag;
    await col.updateIfMatch('1', 'p', { etag: staleEtag }, { name: 'B' }); // etag rotates
    const res = await col.updateIfMatch('1', 'p', { etag: staleEtag }, { name: 'C' });
    expect(res.ok).toBe(false);
    if (!res.ok) expect(res.reason).toBe('conflict');
    expect((await col.findById('1', 'p'))?.name).toBe('B');
  });

  it('stale rev -> conflict before write', async () => {
    await col.updateIfMatch('1', 'p', { rev: 0 }, { name: 'B' }); // rev -> 1
    const res = await col.updateIfMatch('1', 'p', { rev: 0 }, { name: 'C' });
    expect(res.ok).toBe(false);
    if (!res.ok) expect(res.reason).toBe('conflict');
  });

  it('missing doc -> not_found', async () => {
    const res = await col.updateIfMatch('missing', 'p', { rev: 0 }, { name: 'X' });
    expect(res.ok).toBe(false);
    if (!res.ok) expect(res.reason).toBe('not_found');
  });
});
