/**
 * Tests for models-dev-snapshot-loader (#2547 4b).
 *
 * The committed snapshot at `./models-dev-snapshot.json` is the
 * default fixture for the loader — it's generated by the sync script
 * and shipped with the package. These tests verify both the default
 * path (real committed snapshot) and synthetic failure modes.
 */
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';

import { loadModelsDevSnapshot } from './models-dev-snapshot-loader.js';

let tempDir: string;
function tempFile(name: string, content: string): string {
  const path = join(tempDir, name);
  writeFileSync(path, content, 'utf-8');
  return path;
}

beforeEach(() => {
  tempDir = mkdtempSync(join(tmpdir(), 'models-dev-snapshot-test-'));
});

afterEach(() => {
  rmSync(tempDir, { recursive: true, force: true });
});

describe('loadModelsDevSnapshot', () => {
  it('loads the committed snapshot from the default path', () => {
    // No path override — exercise the production default. This passes
    // iff the snapshot file exists and parses cleanly, which is the
    // shipping contract.
    const result = loadModelsDevSnapshot();
    expect(result.status).toBe('loaded');
    expect(result.entries.length).toBeGreaterThan(0);
    expect(typeof result.fetchedAt).toBe('string');
  });

  it('returns missing status when the file is absent', () => {
    const result = loadModelsDevSnapshot({ path: join(tempDir, 'absent.json') });
    expect(result.status).toBe('missing');
    expect(result.entries).toEqual([]);
  });

  it('returns malformed status on broken JSON', () => {
    const path = tempFile('broken.json', '{not valid json');
    const result = loadModelsDevSnapshot({ path });
    expect(result.status).toBe('malformed');
  });

  it('refuses an unsupported version', () => {
    const path = tempFile('wrong-version.json', JSON.stringify({ version: 99, entries: [] }));
    const result = loadModelsDevSnapshot({ path });
    expect(result.status).toBe('malformed');
  });

  it('refuses when entries is not an array', () => {
    const path = tempFile('no-entries.json', JSON.stringify({ version: 1 }));
    const result = loadModelsDevSnapshot({ path });
    expect(result.status).toBe('malformed');
  });

  it('returns entries from a hand-crafted snapshot', () => {
    const path = tempFile(
      'mini.json',
      JSON.stringify({
        version: 1,
        fetchedAt: '2026-05-11',
        sourceUrl: 'https://models.dev/api.json',
        entries: [
          {
            id: 'fake-model',
            vendor: 'anthropic',
            family: 'claude-opus',
            parallelToolCalls: false,
            promptCaching: 'none',
            toolDefinitionFormat: 'openai',
            maxRecommendedTurnBudget: 10,
            strictJson: true,
            quirks: [],
            profileId: 'models-dev-anthropic',
            source: 'models-dev',
          },
        ],
      })
    );
    const result = loadModelsDevSnapshot({ path });
    expect(result.status).toBe('loaded');
    expect(result.entries).toHaveLength(1);
    expect(result.entries[0]!.id).toBe('fake-model');
    expect(result.fetchedAt).toBe('2026-05-11');
  });
});

describe('ModelRegistry default-singleton picks up the snapshot (#2547 4b)', () => {
  it('a known models.dev model id resolves with source=models-dev when no in-tree override exists', async () => {
    // Reset the singleton so this test gets a fresh registry that
    // loads from disk; the committed snapshot contains many entries.
    const { setDefaultRegistry, getDefaultRegistry } = await import('./model-registry.js');
    setDefaultRegistry(undefined);
    try {
      const registry = getDefaultRegistry();
      // Find ANY entry from the snapshot that won't be shadowed by an
      // in-tree authoritative entry. The committed snapshot contains
      // hundreds of model ids; the first one that resolves to source
      // 'models-dev' or 'in-tree' is enough to prove the wiring works.
      // We pick a known-stable id with a recognizable vendor: this is
      // brittle if upstream renames, but the snapshot is committed so
      // a rename would be visible as a diff.
      const result = loadModelsDevSnapshot();
      expect(result.entries.length).toBeGreaterThan(0);
      const first = result.entries[0]!;
      const entry = registry.getEntry(first.id);
      // The wiring is "proven" when the snapshot's per-version
      // capability data (contextWindow / pricing) is reflected in the
      // resolved entry. Three valid sources:
      //   - 'models-dev': snapshot won, identity didn't resolve.
      //   - 'in-tree':    an authoritative override shadowed it.
      //   - 'derived':    snapshot capabilities merged with derived
      //                   behaviour (identity resolved strongly).
      expect(['models-dev', 'in-tree', 'derived']).toContain(entry.source);
      if (entry.source === 'derived' && first.contextWindow !== undefined) {
        expect(entry.contextWindow).toBe(first.contextWindow);
      }
    } finally {
      setDefaultRegistry(undefined);
    }
  });
});
