import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import Fastify from 'fastify';
import { resolveGatewayModel, registerLocalGatewayRoutes } from '../local-gateway.js';
import { loadLlamaProviders } from '../llama-providers.js';

// P0 duplicate-name routing smoke (multi-llama-swap-providers-model-favorites,
// P8): five wire model ids exist on BOTH llama-swap hosts in production
// (deepseek-r1-qwen3-8b et al). Opencode dispatches through the boocode-local
// gateway, so the gateway is the layer that must preserve provider identity —
// the same bare wire name prefixed with different provider ids must reach
// DIFFERENT baseUrls, and an unknown provider must be an error, never a
// silent fallback to whichever host the bare name happens to resolve on.

const DUP = 'deepseek-r1-qwen3-8b';
const SAM_URL = 'http://a.test:8401';
const EMB_URL = 'http://b.test:8411';

function loadFixture(): void {
  const file = {
    defaultProvider: 'sam-desktop',
    providers: [
      { id: 'sam-desktop', label: 'Sam Desktop', baseUrl: SAM_URL, kind: 'llama-swap' },
      { id: 'embedding', label: 'Embedding', baseUrl: EMB_URL, kind: 'llama-swap' },
    ],
  };
  const path = join(tmpdir(), `llama-providers-lgr-${Math.random().toString(36).slice(2)}.json`);
  writeFileSync(path, JSON.stringify(file), 'utf8');
  loadLlamaProviders(path, 'http://legacy.test:8080');
}

describe('local-gateway duplicate-name routing (P0 P8 smoke)', () => {
  beforeEach(() => {
    loadFixture();
  });

  it('routes the same wire name to the intended provider per composite prefix', () => {
    expect(resolveGatewayModel(`sam-desktop/${DUP}`)).toEqual({
      baseUrl: SAM_URL,
      wireModelId: DUP,
    });
    expect(resolveGatewayModel(`embedding/${DUP}`)).toEqual({
      baseUrl: EMB_URL,
      wireModelId: DUP,
    });
  });

  it('resolves a bare id to the default provider, deterministically', () => {
    expect(resolveGatewayModel(DUP)).toEqual({ baseUrl: SAM_URL, wireModelId: DUP });
  });

  it('rejects an unknown provider instead of silently falling back', () => {
    const resolved = resolveGatewayModel(`no-such-host/${DUP}`);
    expect(resolved).toHaveProperty('error');
  });

  describe('through the HTTP route', () => {
    const fetchMock = vi.fn();

    beforeEach(() => {
      vi.stubGlobal('fetch', fetchMock);
      fetchMock.mockReset();
      fetchMock.mockImplementation(
        async () =>
          new Response(JSON.stringify({ id: 'resp', choices: [] }), {
            status: 200,
            headers: { 'content-type': 'application/json' },
          }),
      );
    });

    afterEach(() => {
      vi.unstubAllGlobals();
    });

    it('proxies each composite id to its own host with the bare wire id', async () => {
      const app = Fastify();
      registerLocalGatewayRoutes(app);
      await app.ready();
      try {
        for (const composite of [`sam-desktop/${DUP}`, `embedding/${DUP}`]) {
          const res = await app.inject({
            method: 'POST',
            url: '/v1/chat/completions',
            payload: { model: composite, stream: false, messages: [] },
          });
          expect(res.statusCode).toBe(200);
        }
        const urls = fetchMock.mock.calls.map((c) => String(c[0]));
        expect(urls).toEqual([
          `${SAM_URL}/v1/chat/completions`,
          `${EMB_URL}/v1/chat/completions`,
        ]);
        // The upstream body must carry the BARE wire id — llama-swap knows
        // nothing about composite prefixes.
        const upstreamModels = fetchMock.mock.calls.map(
          (c) => (JSON.parse((c[1] as RequestInit).body as string) as { model: string }).model,
        );
        expect(upstreamModels).toEqual([DUP, DUP]);
      } finally {
        await app.close();
      }
    });

    it('returns 400 for an unknown provider without touching any upstream', async () => {
      const app = Fastify();
      registerLocalGatewayRoutes(app);
      await app.ready();
      try {
        const res = await app.inject({
          method: 'POST',
          url: '/v1/chat/completions',
          payload: { model: `no-such-host/${DUP}`, stream: false, messages: [] },
        });
        expect(res.statusCode).toBe(400);
        expect(fetchMock).not.toHaveBeenCalled();
      } finally {
        await app.close();
      }
    });
  });
});
