import { describe, it, expect, afterEach } from 'vitest';
import { buildGlobalPreamble, __setActiveSessionLookup } from './prompt.js';
import { SSH_CONSOLE_DEFAULTS } from '../../ssh/config.js';

describe('buildGlobalPreamble — conversation recall guidance', () => {
  it('includes proactive recall guidance referencing the conversation tools', () => {
    const out = buildGlobalPreamble([]);
    expect(out).toContain('SearchTaskConversation');
    expect(out).toContain('ReadTaskConversation');
    // Should tell the agent to search before asking the user to repeat info.
    expect(out.toLowerCase()).toContain('conversation recall');
  });
});

describe('buildGlobalPreamble — file provenance guidance', () => {
  it('tells the agent to check provenance before editing user_input / other-task files', () => {
    const out = buildGlobalPreamble([]);
    expect(out).toContain('GetFileProvenance');
    expect(out).toContain('ListWorkspaceFiles');
    expect(out).toContain('user_input');
    expect(out).toContain('created_by_task_id');
  });
});

describe('buildGlobalPreamble — mission brief rendering of new fields', () => {
  it('renders user_constraints / decisions / current_focus when present', () => {
    const out = buildGlobalPreamble([], {
      missionBrief: {
        goal: 'ship the feature',
        done: '',
        open: '',
        clarifications: '',
        user_constraints: 'keep auth flow unchanged',
        decisions: 'extend the brief, not a new store',
        current_focus: 'wiring conversation tools',
      },
    });
    expect(out).toContain('User constraints');
    expect(out).toContain('keep auth flow unchanged');
    expect(out).toContain('Decisions');
    expect(out).toContain('extend the brief, not a new store');
    expect(out).toContain('Current focus');
    expect(out).toContain('wiring conversation tools');
  });

  it('still renders a legacy brief with only the original fields', () => {
    const out = buildGlobalPreamble([], {
      missionBrief: { goal: 'legacy goal', done: 'd', open: 'o', clarifications: 'c' },
    });
    expect(out).toContain('legacy goal');
    expect(out).not.toContain('User constraints');
  });

  // Acceptance: "Mission Brief rendering keeps a bounded prompt size." A brief far
  // over the char budget must be truncated, not rendered whole.
  it('truncates an oversized mission brief to keep the prompt bounded', () => {
    const huge = 'x'.repeat(8000);
    const withBrief = buildGlobalPreamble([], {
      missionBrief: { goal: 'g', done: huge, open: huge, clarifications: '', user_constraints: huge },
    });
    const withoutBrief = buildGlobalPreamble([], { missionBrief: { goal: 'g', done: '', open: '', clarifications: '' } });
    // The 24000 chars of field content must not survive verbatim.
    expect(withBrief).toContain('[truncated]');
    expect(withBrief.length).toBeLessThan(withoutBrief.length + 5000);
  });
});

describe('buildGlobalPreamble — SSH console screen scoped to allowed connections (Task 5)', () => {
  afterEach(() => {
    __setActiveSessionLookup(null);
  });

  function makeSession(connectionId: string, lineCount: number) {
    const text = Array.from({ length: lineCount }, (_, i) => `${connectionId}-line-${i + 1}`).join('\n');
    return {
      connectionId,
      cols: 80,
      rows: 24,
      snapshotScreen: () => ({ text }),
    };
  }

  it('injects only the allowed connection\'s screen when the movement restricts allowedSshConnections', () => {
    __setActiveSessionLookup((_taskId: string) => [
      makeSession('connA', 30),
      makeSession('connB', 30),
    ]);

    const out = buildGlobalPreamble([], {
      taskId: 't1',
      movementForConsole: { allowedTools: ['SshConsoleSend'], allowedSshConnections: ['connA'] },
    });

    expect(out).toContain('connA-line-30');
    expect(out).not.toContain('connB-line-30');
    expect(out).not.toContain('connB');
  });

  it('injects every open session when allowedSshConnections is the wildcard', () => {
    __setActiveSessionLookup((_taskId: string) => [
      makeSession('connA', 5),
      makeSession('connB', 5),
    ]);

    const out = buildGlobalPreamble([], {
      taskId: 't1',
      movementForConsole: { allowedTools: ['SshConsoleSend'], allowedSshConnections: ['*'] },
    });

    expect(out).toContain('connA-line-5');
    expect(out).toContain('connB-line-5');
  });

  it('trims each injected screen block to the configured default (SSH_CONSOLE_DEFAULTS.autoInjectScreenLines)', () => {
    __setActiveSessionLookup((_taskId: string) => [
      makeSession('connA', 50),
      makeSession('connB', 50),
    ]);

    const out = buildGlobalPreamble([], {
      taskId: 't1',
      movementForConsole: { allowedTools: ['SshConsoleSend'], allowedSshConnections: ['*'] },
    });

    const maxLines = SSH_CONSOLE_DEFAULTS.autoInjectScreenLines;
    const fences = out.match(/```\n([\s\S]*?)\n```/g) ?? [];
    // Two open sessions → two fenced screen blocks, each capped.
    expect(fences.length).toBe(2);
    for (const fence of fences) {
      const body = fence.replace(/```\n?/g, '');
      const lineCount = body.split('\n').filter((l) => l.length > 0).length;
      expect(lineCount).toBeLessThanOrEqual(maxLines);
    }
    // The tail of each screen (most recent lines) must survive the trim.
    expect(out).toContain('connA-line-50');
    expect(out).toContain('connB-line-50');
    // Early lines from the 50-line screens must have been trimmed away.
    expect(out).not.toContain('connA-line-1\n');
    expect(out).not.toContain('connB-line-1\n');
  });

  it('single-session tasks still render one screen block (unchanged from before)', () => {
    __setActiveSessionLookup((_taskId: string) => [makeSession('connOnly', 3)]);

    const out = buildGlobalPreamble([], {
      taskId: 't1',
      movementForConsole: { allowedTools: ['SshConsoleSend'], allowedSshConnections: ['*'] },
    });

    const screenHeadings = out.match(/## Console screen/g) ?? [];
    expect(screenHeadings.length).toBe(1);
    expect(out).toContain('connOnly-line-3');
  });
});
