import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { DavePresence, localClock } from '../lib/agent/dave-presence.mjs';

function setup({ result, profile = {}, random = () => 0 } = {}) {
  const messages = [];
  const calls = [];
  const statePath = join(mkdtempSync(join(tmpdir(), 'dave-presence-')), 'state.json');
  const presence = new DavePresence({
    agent: {
      isConfigured: true,
      async run(prompt, options) {
        calls.push({ prompt, options });
        return result || { answer: 'Die Lage ist ruhig und die Daten sind aktuell.', notify: true, priority: 'routine', evidence: ['evt-1'], trace: [] };
      },
    },
    alerter: {
      isConfigured: true,
      async sendMessage(text) { messages.push(text); return { ok: true }; },
    },
    profileStore: {
      getAgentProfile() {
        return { language: 'de', timezone: 'UTC', quietHours: null, alertPreference: 'all', ...profile };
      },
    },
    getContext: () => '{"status":"healthy"}',
    getRuntime: () => ({ delta: { summary: { totalChanges: 0, criticalChanges: 0 } } }),
    statePath,
    random,
    config: {
      enabled: true,
      maxPerDay: 4,
      minGapMinutes: 15,
      minIntervalMinutes: 15,
      maxIntervalMinutes: 30,
      idleAfterMinutes: 15,
      checkIntervalMinutes: 5,
      timezone: 'UTC',
    },
  });
  return { presence, messages, calls, statePath };
}

test('dynamic presence sends a grounded message and persists a variable next evaluation', async () => {
  const { presence, messages, calls } = setup({ random: () => 0.5 });
  const now = new Date('2026-07-06T12:00:00Z');
  const outcome = await presence.tick(now);
  assert.deepEqual(outcome, { sent: true, reason: 'sent' });
  assert.match(messages[0], /^Die Lage ist ruhig/);
  assert.doesNotMatch(messages[0], /\[DAVE \/\/ AKTIV\]/);
  assert.match(messages[0], /Belege:/);
  assert.match(calls[0].prompt, /dynamic presence evaluation, not a fixed scheduled briefing/i);
  assert.equal(calls[0].options.mode, 'presence');
  assert.equal(calls[0].options.preferredLanguage, 'de');
  assert.equal(presence.status().sentToday, 1);
  assert.equal(presence.status().nextEvaluationAt, '2026-07-06T12:22:30.000Z');
  assert.equal((await presence.tick(new Date('2026-07-06T12:05:00Z'))).reason, 'not_due');
});

test('material sweep changes dynamically pull the next evaluation forward', () => {
  const { presence } = setup();
  const now = new Date('2026-07-06T12:00:00Z');
  presence.noteUserInteraction(now);
  assert.equal(presence.status().nextEvaluationAt, '2026-07-06T12:15:00.000Z');
  assert.equal(presence.nudge({ summary: { criticalChanges: 1, totalChanges: 1 } }, now), true);
  assert.equal(presence.status().nextEvaluationAt, '2026-07-06T12:05:00.000Z');
});

test('recent operator activity delays unsolicited conversation', async () => {
  const { presence, messages } = setup();
  const now = new Date('2026-07-06T12:00:00Z');
  presence.noteUserInteraction(now);
  const outcome = await presence.tick(new Date('2026-07-06T12:01:00Z'));
  assert.equal(outcome.reason, 'not_due');
  assert.equal(messages.length, 0);
});

test('quiet hours and mutating proposals are suppressed', async () => {
  const quiet = setup({ profile: { quietHours: '00:00-00:00' } });
  assert.equal((await quiet.presence.tick(new Date('2026-07-06T12:00:00Z'))).reason, 'quiet_hours');
  assert.equal(quiet.messages.length, 0);

  const mutation = setup({ result: { answer: 'Confirmation required.', notify: true, pendingAction: { tool: 'trigger_sweep' } } });
  assert.equal((await mutation.presence.tick(new Date('2026-07-06T12:00:00Z'))).reason, 'mutation_rejected');
  assert.equal(mutation.messages.length, 0);
});

test('local clock uses the profile timezone', () => {
  assert.deepEqual(localClock(new Date('2026-07-06T12:30:00Z'), 'Europe/Berlin'), { day: '2026-07-06', time: '14:30' });
  assert.equal(localClock(new Date(), 'Invalid/Timezone'), null);
});

test('invalid profile timezone falls back to configured operational timezone', async () => {
  const { presence, messages, calls } = setup({ profile: { timezone: 'not-a-timezone' } });
  const outcome = await presence.tick(new Date('2026-07-06T12:00:00Z'));
  assert.equal(outcome.reason, 'sent');
  assert.equal(messages.length, 1);
  assert.match(calls[0].prompt, /timezone: UTC/);
});
