import { execFile } from 'node:child_process';
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { promisify } from 'node:util';
import { describe, expect, it } from 'vitest';
import { generateAgentEnv, loadFleetRoster } from '../commands/fleet.js';
import {
  provisionInteractionService,
  readInteractionServiceProfile,
} from './interaction-service-profile.js';
import type { InteractionServiceProfileError } from './interaction-service-profile.js';

const frameworkFleet = resolve(
  dirname(fileURLToPath(import.meta.url)),
  '..',
  '..',
  'framework',
  'fleet',
);
const profilePath = join(frameworkFleet, 'services', 'operator-interaction.yaml');
const examplePath = join(frameworkFleet, 'examples', 'operator-interaction.yaml');
const policyScript = join(
  frameworkFleet,
  '..',
  'tools',
  'fleet',
  'print-interaction-effective-policy.sh',
);
const interactionStartScript = join(
  frameworkFleet,
  '..',
  'tools',
  'fleet',
  'start-interaction-service.sh',
);
const agentStartScript = join(frameworkFleet, '..', 'tools', 'fleet', 'start-agent-session.sh');
const execFileAsync = promisify(execFile);

describe('operator interaction service profile', (): void => {
  it('provisions a user-supplied Nova identity as data with the required effective policy', async (): Promise<void> => {
    const profile = await readInteractionServiceProfile(profilePath);
    const provisioned = provisionInteractionService(profile, { agentName: 'Nova' });

    expect(provisioned.rosterAgent).toEqual({
      name: 'Nova',
      runtime: 'pi',
      className: 'operator-interaction',
      modelHint: 'openai/gpt-5.6-sol',
      reasoningLevel: 'high',
      toolPolicy: 'operator-interaction',
      persistentPersona: true,
    });
    expect(provisioned.effectivePolicy).toEqual({
      agentName: 'Nova',
      runtime: 'pi',
      model: 'openai/gpt-5.6-sol',
      reasoning: 'high',
      toolPolicy: 'operator-interaction',
    });
    expect(JSON.stringify(provisioned.effectivePolicy)).not.toMatch(
      /token|secret|credential|api.?key/i,
    );
  });

  it('accepts a renamed example roster and surfaces only its effective policy', async (): Promise<void> => {
    const directory = await mkdtemp(join(tmpdir(), 'mosaic-interaction-profile-'));
    const rosterPath = join(directory, 'roster.yaml');
    try {
      const example = await readFile(examplePath, 'utf8');
      await writeFile(rosterPath, example.replace('name: Tess', 'name: Nova'), 'utf8');
      const roster = await loadFleetRoster(rosterPath);
      const agent = roster.agents[0]!;

      expect(agent.name).toBe('Nova');
      expect(agent.reasoningLevel).toBe('high');
      expect(agent.toolPolicy).toBe('operator-interaction');
      expect(generateAgentEnv(roster, agent)).toContain('MOSAIC_AGENT_NAME=Nova');
      expect(generateAgentEnv(roster, agent)).toContain('MOSAIC_AGENT_REASONING=high');

      const { stdout } = await execFileAsync(policyScript, [], {
        env: {
          ...process.env,
          MOSAIC_AGENT_NAME: agent.name,
          MOSAIC_AGENT_RUNTIME: agent.runtime,
          MOSAIC_AGENT_MODEL: agent.modelHint,
          MOSAIC_AGENT_REASONING: agent.reasoningLevel,
          MOSAIC_AGENT_TOOL_POLICY: agent.toolPolicy,
        },
      });
      expect(JSON.parse(stdout)).toEqual({
        agentName: 'Nova',
        runtime: 'pi',
        model: 'openai/gpt-5.6-sol',
        reasoning: 'high',
        toolPolicy: 'operator-interaction',
      });
    } finally {
      await rm(directory, { recursive: true, force: true });
    }
  });

  it('fails before launch when service environment drifts from the pinned policy', async (): Promise<void> => {
    await expect(
      execFileAsync(interactionStartScript, ['Nova'], {
        env: {
          ...process.env,
          MOSAIC_AGENT_NAME: 'Nova',
          MOSAIC_AGENT_RUNTIME: 'pi',
          MOSAIC_AGENT_MODEL: 'other/model',
          MOSAIC_AGENT_REASONING: 'high',
          MOSAIC_AGENT_TOOL_POLICY: 'operator-interaction',
        },
      }),
    ).rejects.toMatchObject({ code: 64 });
  });

  it('rejects unsafe names from the policy printer and reasoning before tmux launch', async (): Promise<void> => {
    await expect(
      execFileAsync(policyScript, [], {
        env: {
          ...process.env,
          MOSAIC_AGENT_NAME: 'Nova"bad',
          MOSAIC_AGENT_RUNTIME: 'pi',
          MOSAIC_AGENT_MODEL: 'openai/gpt-5.6-sol',
          MOSAIC_AGENT_REASONING: 'high',
          MOSAIC_AGENT_TOOL_POLICY: 'operator-interaction',
        },
      }),
    ).rejects.toMatchObject({ code: 64 });
    await expect(
      execFileAsync(agentStartScript, ['Nova'], {
        env: { ...process.env, MOSAIC_AGENT_REASONING: 'high; id' },
      }),
    ).rejects.toMatchObject({ code: 64 });
  });

  it('keeps the product name confined to an example instance, not service source or defaults', async (): Promise<void> => {
    const [profile, source, example] = await Promise.all([
      readFile(profilePath, 'utf8'),
      readFile(new URL('./interaction-service-profile.ts', import.meta.url), 'utf8'),
      readFile(examplePath, 'utf8'),
    ]);

    expect(profile).not.toMatch(/tess/i);
    expect(source).not.toMatch(/tess/i);
    expect(example).toMatch(/name: Tess/);
  });

  it('fails fast when a required policy field is absent or changed from the pinned policy', async (): Promise<void> => {
    await expect(readInteractionServiceProfile(profilePath, { model: '' })).rejects.toMatchObject({
      code: 'invalid_profile',
    } satisfies Partial<InteractionServiceProfileError>);
    await expect(
      readInteractionServiceProfile(profilePath, { reasoning: 'medium' }),
    ).rejects.toMatchObject({
      code: 'invalid_profile',
    } satisfies Partial<InteractionServiceProfileError>);
  });
});
