'use strict';

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const http = require('http');
const os = require('os');
const path = require('path');
const piProvider = require('../lib/pi-provider');

function tempConfig() {
  return path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'clawd-pi-')), 'models.json');
}

function apply(provider, options) {
  return new Promise((resolve) => piProvider.applyFullProviderFromVps(provider, resolve, options));
}

function refresh(options) {
  return new Promise((resolve) => piProvider.refreshModelsIfChanged(resolve, options));
}

function read(file) {
  return JSON.parse(fs.readFileSync(file, 'utf8'));
}

const payload = {
  name: 'cutos',
  'base-url': 'https://api.example.test',
  'api-key': 'test-key',
};

test('creates a Pi models file and maps models to reasoning entries', async () => {
  const configFile = tempConfig();
  await apply(payload, {
    configFile,
    fetchModels: (_url, _key, done) => done(null, [
      { id: 'gpt-test', reasoning: true },
      { id: 'glm-test', reasoning: true },
    ]),
  });
  assert.deepEqual(read(configFile), {
    providers: {
      cutos: {
        baseUrl: 'https://api.example.test/v1',
        api: 'openai-responses',
        apiKey: 'test-key',
        models: [
          { id: 'gpt-test', reasoning: true },
          { id: 'glm-test', reasoning: true },
        ],
      },
    },
  });
});

test('preserves unrelated top-level fields and providers when upserting', async () => {
  const configFile = tempConfig();
  fs.writeFileSync(configFile, JSON.stringify({
    theme: 'dark',
    providers: { custom: { baseUrl: 'http://localhost:1234' } },
  }));
  await apply(payload, {
    configFile,
    fetchModels: (_url, _key, done) => done(null, [{ id: 'gpt-test', reasoning: true }]),
  });
  const config = read(configFile);
  assert.equal(config.theme, 'dark');
  assert.deepEqual(config.providers.custom, { baseUrl: 'http://localhost:1234' });
  assert.equal(config.providers.cutos.api, 'openai-responses');
});

test('retains existing models when model refresh fails while updating credentials', async () => {
  const configFile = tempConfig();
  fs.writeFileSync(configFile, JSON.stringify({ providers: { cutos: {
    baseUrl: 'https://old.example/v1',
    api: 'openai-responses',
    apiKey: 'old-key',
    models: [{ id: 'existing-model', reasoning: true }],
  } } }));
  await apply(payload, {
    configFile,
    fetchModels: (_url, _key, done) => done(new Error('offline')),
  });
  const provider = read(configFile).providers.cutos;
  assert.equal(provider.apiKey, 'test-key');
  assert.deepEqual(provider.models, [{ id: 'existing-model', reasoning: true }]);
});

test('removes only the named provider on unbind', () => {
  const configFile = tempConfig();
  fs.writeFileSync(configFile, JSON.stringify({ providers: { cutos: {}, custom: { keep: true } } }));
  piProvider.removeProviderByName('cutos', { configFile });
  assert.deepEqual(read(configFile), { providers: { custom: { keep: true } } });
});

test('unbind cancels an in-flight provider write', async () => {
  const configFile = tempConfig();
  fs.writeFileSync(configFile, JSON.stringify({ providers: { cutos: {}, custom: { keep: true } } }));
  let finishFetch;
  const pending = apply(payload, {
    configFile,
    fetchModels: (_url, _key, done) => { finishFetch = done; },
  });
  piProvider.removeProviderByName('cutos', { configFile });
  finishFetch(null, [{ id: 'late-model', reasoning: true }]);
  await pending;
  assert.deepEqual(read(configFile), { providers: { custom: { keep: true } } });
});

test('refresh updates models only when their ids change', async () => {
  const configFile = tempConfig();
  fs.writeFileSync(configFile, JSON.stringify({ providers: { cutos: {
    baseUrl: 'https://api.example.test/v1', api: 'openai-responses', apiKey: 'key',
    models: [{ id: 'old', reasoning: true }],
  } } }));
  await refresh({
    configFile,
    fetchModels: (_url, _key, done) => done(null, [{ id: 'new', reasoning: true }]),
  });
  assert.deepEqual(read(configFile).providers.cutos.models, [{ id: 'new', reasoning: true }]);
});

test('fetchModels reads OpenAI model responses without exposing the key', async () => {
  const server = http.createServer((req, res) => {
    assert.equal(req.url, '/v1/models');
    assert.equal(req.headers.authorization, 'Bearer private-test-key');
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify({ data: [{ id: 'model-a' }, { id: 'model-b' }] }));
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  try {
    const address = server.address();
    const models = await new Promise((resolve, reject) => {
      piProvider.fetchModels(`http://127.0.0.1:${address.port}`, 'private-test-key', (error, result) => {
        if (error) reject(error); else resolve(result);
      });
    });
    assert.deepEqual(models, [
      { id: 'model-a', reasoning: true },
      { id: 'model-b', reasoning: true },
    ]);
  } finally {
    await new Promise((resolve) => server.close(resolve));
  }
});
