import test from 'node:test';
import assert from 'node:assert/strict';
import { TerminalAgent, TerminalToolRegistry } from '../lib/agent/terminal-agent.mjs';
import { createTerminalToolRegistry } from '../lib/agent/terminal-tools.mjs';

function providerWith(decisions) {
  let index = 0;
  return {
    isConfigured: true,
    async complete() {
      const decision = decisions[Math.min(index++, decisions.length - 1)];
      return { text: JSON.stringify(decision) };
    },
  };
}

test('terminal agent performs bounded multi-step tool reasoning', async () => {
  const registry = new TerminalToolRegistry([
    { name: 'get_status', description: 'status', handler: async () => ({ status: 'degraded' }) },
    { name: 'search_memory', description: 'memory', handler: async args => ({ query: args.query, hits: 2 }) },
  ]);
  const agent = new TerminalAgent({
    registry,
    provider: providerWith([
      { type: 'tool_call', tool: 'get_status', arguments: {}, rationale: 'Check freshness' },
      { type: 'tool_call', tool: 'search_memory', arguments: { query: 'Iran' }, rationale: 'Compare history' },
      { type: 'final', answer: 'Two historical events support the current signal.', confidence: 'medium', evidence: ['evt-1'], notify: false, priority: 'routine' },
    ]),
    maxSteps: 4,
  });

  const result = await agent.run('What changed?', { chatId: 42 });
  assert.equal(result.answer, 'Two historical events support the current signal.');
  assert.equal(result.confidence, 'medium');
  assert.deepEqual(result.trace.map(item => item.tool), ['get_status', 'search_memory']);
  assert.ok(result.trace.every(item => item.status === 'ok'));
});

test('mutating tools require chat-bound confirmation', async () => {
  let executions = 0;
  const registry = new TerminalToolRegistry([{
    name: 'trigger_sweep',
    description: 'sweep',
    mutating: true,
    handler: async (_args, runtime) => {
      assert.equal(runtime.confirmed, true);
      executions++;
      return { accepted: true };
    },
  }]);
  const agent = new TerminalAgent({
    registry,
    provider: providerWith([{ type: 'tool_call', tool: 'trigger_sweep', arguments: {}, rationale: 'Fresh data needed' }]),
  });

  const proposal = await agent.run('Run a sweep', { chatId: 42 });
  assert.equal(executions, 0);
  assert.equal(proposal.pendingAction.tool, 'trigger_sweep');
  assert.equal((await agent.confirm(proposal.pendingAction.id, 99)).ok, false);
  assert.equal(executions, 0);
  assert.equal((await agent.confirm(proposal.pendingAction.id, 42)).ok, true);
  assert.equal(executions, 1);
  assert.equal((await agent.confirm(proposal.pendingAction.id, 42)).ok, false);
});

test('unknown tools fail closed and remain in audit trace', async () => {
  const agent = new TerminalAgent({
    registry: new TerminalToolRegistry([]),
    provider: providerWith([
      { type: 'tool_call', tool: 'run_shell', arguments: { command: 'whoami' }, rationale: 'Not allowed' },
      { type: 'final', answer: 'That operation is not available.', confidence: 'high', evidence: [], notify: false, priority: 'routine' },
    ]),
  });
  const result = await agent.run('Run shell', { chatId: 42 });
  assert.equal(result.answer, 'That operation is not available.');
  assert.deepEqual(result.trace[0], { tool: 'run_shell', status: 'rejected', durationMs: 0, rationale: 'Not allowed' });
});

test('proactive notifications observe cooldown', async () => {
  const agent = new TerminalAgent({
    registry: new TerminalToolRegistry([]),
    provider: providerWith([{ type: 'final', answer: 'Material escalation detected.', confidence: 'high', evidence: ['https://example.test'], notify: true, priority: 'flash' }]),
    proactiveCooldownMs: 60000,
  });
  const first = await agent.analyzeProactively('Evaluate');
  const second = await agent.analyzeProactively('Evaluate again');
  assert.equal(first.notify, true);
  assert.equal(first.priority, 'flash');
  assert.equal(second.notify, false);
  assert.equal(second.suppressed, 'cooldown');
});

test('step exhaustion uses a final-only prompt and returns synthesized evidence', async () => {
  const prompts = [];
  let call = 0;
  const provider = {
    isConfigured: true,
    async complete(system) {
      prompts.push(system);
      call++;
      return call === 1
        ? { text: JSON.stringify({ type: 'tool_call', tool: 'get_evidence', arguments: {}, rationale: 'Verify claim' }) }
        : { text: JSON.stringify({ type: 'final', answer: 'The available evidence does not confirm the claim.', confidence: 'medium', evidence: ['evt-1'], notify: false, priority: 'routine' }) };
    },
  };
  const agent = new TerminalAgent({
    provider,
    registry: new TerminalToolRegistry([{ name: 'get_evidence', handler: async () => [{ id: 'evt-1' }] }]),
    maxSteps: 1,
  });

  const result = await agent.run('Is the claim confirmed?');
  assert.equal(result.answer, 'The available evidence does not confirm the claim.');
  assert.match(prompts[1], /Tool use is finished and unavailable/);
  assert.doesNotMatch(prompts[1], /ALLOWLISTED TOOLS/);
  assert.match(prompts[0], /Your name is DAVE/);
  assert.match(prompts[0], /ADAPTIVE WRITING STYLE/);
  assert.match(prompts[1], /Your name is DAVE/);
});

test('repeated tool calls during finalization fail closed without leaking protocol JSON', async () => {
  const provider = providerWith([{ type: 'tool_call', tool: 'get_evidence', arguments: {}, rationale: 'Keep searching' }]);
  const agent = new TerminalAgent({
    provider,
    registry: new TerminalToolRegistry([{ name: 'get_evidence', handler: async () => [] }]),
    maxSteps: 1,
  });

  const result = await agent.run('Wie sieht es mit dem Angriff aus?');
  assert.match(result.answer, /nicht zuverlässig/);
  assert.doesNotMatch(result.answer, /tool_call|get_evidence|rationale/i);
  assert.equal(result.notify, false);
});

test('empty model responses are repaired instead of shown to the operator', async () => {
  let call = 0;
  const agent = new TerminalAgent({
    provider: {
      isConfigured: true,
      async complete() {
        call++;
        return call === 1
          ? { text: '' }
          : { text: JSON.stringify({ type: 'final', answer: 'Ich pruefe den letzten Sweep weiter.', confidence: 'low', evidence: [], notify: false, priority: 'routine' }) };
      },
    },
    registry: new TerminalToolRegistry([]),
  });

  const result = await agent.run('Ist noch woanders was passiert?', { preferredLanguage: 'de' });
  assert.equal(result.answer, 'Ich pruefe den letzten Sweep weiter.');
  assert.doesNotMatch(result.answer, /no usable response/i);
});

test('provider timeout falls back to the local sweep snapshot', async () => {
  const agent = new TerminalAgent({
    provider: {
      isConfigured: true,
      async complete() {
        throw new Error('The operation was aborted due to timeout');
      },
    },
    registry: new TerminalToolRegistry([]),
  });
  const context = JSON.stringify({
    news: [{ title: 'Regional escalation report' }],
    urgentOsint: ['OSINT: power outage near border'],
    degradedSources: [{ name: 'GDELT' }, { name: 'ACLED' }],
    ideas: [{ title: 'Gold hedge' }],
  });

  const result = await agent.run('Auch auf andere Teile der Welt', { preferredLanguage: 'de', context });
  assert.match(result.answer, /keine stabile Modellantwort/);
  assert.match(result.answer, /letzten lokalen Sweep/);
  assert.match(result.answer, /OSINT: power outage near border/);
  assert.match(result.answer, /Steuern kann ich per Chat/);
  assert.equal(result.trace[0].tool, 'llm');
  assert.equal(result.trace[0].status, 'failed');
});

test('agent propagates preferred German language through tool and finalization prompts', async () => {
  const prompts = [];
  let call = 0;
  const agent = new TerminalAgent({
    provider: {
      isConfigured: true,
      async complete(systemPrompt) {
        prompts.push(systemPrompt);
        call++;
        return call === 1
          ? { text: JSON.stringify({ type: 'tool_call', tool: 'get_status', arguments: {}, rationale: 'Status prüfen' }) }
          : { text: JSON.stringify({ type: 'final', answer: 'Die Systemlage ist stabil.', confidence: 'high', evidence: [], notify: false, priority: 'routine' }) };
      },
    },
    registry: new TerminalToolRegistry([{ name: 'get_status', handler: async () => ({ status: 'healthy' }) }]),
    maxSteps: 1,
  });

  const result = await agent.run('Lage?', { preferredLanguage: 'de' });
  assert.equal(result.answer, 'Die Systemlage ist stabil.');
  assert.match(prompts[0], /REQUIRED RESPONSE LANGUAGE: German \(de\)/);
  assert.match(prompts[1], /REQUIRED RESPONSE LANGUAGE: German \(de\)/);
});

test('scheduled presence cannot create pending mutating actions', async () => {
  let executions = 0;
  const agent = new TerminalAgent({
    provider: providerWith([
      { type: 'tool_call', tool: 'trigger_sweep', arguments: {}, rationale: 'Refresh data' },
      { type: 'final', answer: 'No autonomous action was taken.', confidence: 'high', evidence: [], notify: false, priority: 'routine' },
    ]),
    registry: new TerminalToolRegistry([{
      name: 'trigger_sweep',
      mutating: true,
      handler: async () => { executions++; },
    }]),
  });

  const result = await agent.run('Evaluate presence', { mode: 'presence' });
  assert.equal(result.pendingAction, undefined);
  assert.equal(result.trace[0].status, 'rejected');
  assert.equal(executions, 0);
});

test('ChatML-style tagged tool calls are executed without leaking protocol text', async () => {
  const calls = [];
  let responseIndex = 0;
  const responses = [
    '<|tool_call>call:get_evidence{query:"Russia planned attack military escalation",limit:5}<tool_call|>',
    JSON.stringify({ type: 'final', answer: 'The retrieved evidence does not confirm a specific planned attack.', confidence: 'medium', evidence: ['evt-russia'], notify: false, priority: 'routine' }),
  ];
  const agent = new TerminalAgent({
    provider: {
      isConfigured: true,
      async complete() { return { text: responses[responseIndex++] }; },
    },
    registry: new TerminalToolRegistry([{
      name: 'get_evidence',
      handler: async args => { calls.push(args); return [{ id: 'evt-russia' }]; },
    }]),
  });

  const result = await agent.run('Wie sieht es mit dem angeblich geplanten Angriff von Russland aus?');
  assert.deepEqual(calls, [{ query: 'Russia planned attack military escalation', limit: 5 }]);
  assert.equal(result.trace[0].tool, 'get_evidence');
  assert.doesNotMatch(result.answer, /tool_call|call:get_evidence/i);
});

test('malformed tagged protocol is repaired instead of returned to the user', async () => {
  let responseIndex = 0;
  const responses = [
    '<|tool_call>call:get_evidence{broken arguments}<tool_call|>',
    JSON.stringify({ type: 'final', answer: 'I could not run the malformed tool request.', confidence: 'low', evidence: [], notify: false, priority: 'routine' }),
  ];
  const agent = new TerminalAgent({
    provider: { isConfigured: true, async complete() { return { text: responses[responseIndex++] }; } },
    registry: new TerminalToolRegistry([]),
  });

  const result = await agent.run('Check the claim');
  assert.equal(result.answer, 'I could not run the malformed tool request.');
  assert.doesNotMatch(result.answer, /tool_call|call:get_evidence/i);
});

test('terminal registry exposes bounded public web search as a read-only tool', async () => {
  const originalFetch = globalThis.fetch;
  let requestedUrl = '';
  globalThis.fetch = async (url) => {
    requestedUrl = String(url);
    return {
      ok: true,
      status: 200,
      headers: { get: () => 'application/json' },
      text: async () => JSON.stringify({
        articles: [{
          title: 'Verified report',
          sourceCommonName: 'Example News',
          url: 'https://example.test/report',
          seendate: '20260707100000',
          language: 'English',
        }],
      }),
    };
  };

  try {
    const registry = createTerminalToolRegistry();
    const tool = registry.get('web_search');
    assert.equal(tool.mutating, false);
    const result = await registry.execute('web_search', { query: 'Russia attack claim', limit: 3, hours: 24 });
    assert.match(requestedUrl, /api\.gdeltproject\.org\/api\/v2\/doc\/doc/);
    assert.equal(result.query, 'Russia attack claim');
    assert.equal(result.results.length, 1);
    assert.equal(result.results[0].url, 'https://example.test/report');
  } finally {
    globalThis.fetch = originalFetch;
  }
});
