import { describe, it, expect, vi } from 'vitest';
import { resolveServerEnv, type SecretResolver } from '../src/services/env-resolver.js';
import type { McpServer } from '@prisma/client';

function makeServer(env: unknown[]): McpServer {
  return {
    id: 'srv-1',
    name: 'test-server',
    description: '',
    packageName: null,
    dockerImage: 'test:latest',
    transport: 'STDIO',
    repositoryUrl: null,
    externalUrl: null,
    command: null,
    containerPort: null,
    replicas: 1,
    env,
    version: 1,
    createdAt: new Date(),
    updatedAt: new Date(),
  } as McpServer;
}

/** A SecretResolver backed by a {secretName: {key: value}} map. */
function mockResolver(secrets: Record<string, Record<string, string>>): SecretResolver {
  return {
    resolve: vi.fn(async (name: string, key: string): Promise<string> => {
      const data = secrets[name];
      if (!data) throw new Error(`Secret '${name}' not found`);
      const value = data[key];
      if (value === undefined) throw new Error(`Key '${key}' not found in secret '${name}'`);
      return value;
    }),
  };
}

describe('resolveServerEnv', () => {
  it('resolves inline values', async () => {
    const server = makeServer([
      { name: 'FOO', value: 'bar' },
      { name: 'BAZ', value: 'qux' },
    ]);
    const result = await resolveServerEnv(server, mockResolver({}));
    expect(result).toEqual({ FOO: 'bar', BAZ: 'qux' });
  });

  it('resolves secret references', async () => {
    const server = makeServer([
      { name: 'TOKEN', valueFrom: { secretRef: { name: 'ha-creds', key: 'HOMEASSISTANT_TOKEN' } } },
    ]);
    const resolver = mockResolver({ 'ha-creds': { HOMEASSISTANT_TOKEN: 'secret-token-123' } });
    const result = await resolveServerEnv(server, resolver);
    expect(result).toEqual({ TOKEN: 'secret-token-123' });
  });

  it('handles mixed inline and secret refs', async () => {
    const server = makeServer([
      { name: 'URL', value: 'https://ha.local' },
      { name: 'TOKEN', valueFrom: { secretRef: { name: 'creds', key: 'TOKEN' } } },
    ]);
    const resolver = mockResolver({ creds: { TOKEN: 'my-token' } });
    const result = await resolveServerEnv(server, resolver);
    expect(result).toEqual({ URL: 'https://ha.local', TOKEN: 'my-token' });
  });

  it('calls the resolver once per distinct ref', async () => {
    const server = makeServer([
      { name: 'A', valueFrom: { secretRef: { name: 'shared', key: 'KEY_A' } } },
      { name: 'B', valueFrom: { secretRef: { name: 'shared', key: 'KEY_B' } } },
    ]);
    const resolver = mockResolver({ shared: { KEY_A: 'val-a', KEY_B: 'val-b' } });
    const result = await resolveServerEnv(server, resolver);
    expect(result).toEqual({ A: 'val-a', B: 'val-b' });
    // Resolver is called per-entry now — caching moved to the SecretService layer,
    // which is where downstream drivers can be hit at most once per (name, key) pair.
    expect(resolver.resolve).toHaveBeenCalledTimes(2);
  });

  it('throws when secret not found', async () => {
    const server = makeServer([
      { name: 'TOKEN', valueFrom: { secretRef: { name: 'missing', key: 'TOKEN' } } },
    ]);
    await expect(resolveServerEnv(server, mockResolver({}))).rejects.toThrow(/Secret 'missing' not found/);
  });

  it('throws when secret key not found', async () => {
    const server = makeServer([
      { name: 'TOKEN', valueFrom: { secretRef: { name: 'creds', key: 'NONEXISTENT' } } },
    ]);
    const resolver = mockResolver({ creds: { OTHER_KEY: 'val' } });
    await expect(resolveServerEnv(server, resolver)).rejects.toThrow(/Key 'NONEXISTENT' not found/);
  });

  it('returns empty map for empty env', async () => {
    const server = makeServer([]);
    const result = await resolveServerEnv(server, mockResolver({}));
    expect(result).toEqual({});
  });
});
