'use strict';

/**
 * Integration tests for session-monitor.js
 * Tests session detection by writing mock sessions.json files.
 */

const { describe, it, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const os = require('os');

const { SessionMonitor } = require('../../src/session-monitor');

function createTmpDir() {
  return fs.mkdtempSync(path.join(os.tmpdir(), 'sm-test-'));
}

function writeSessionsJson(dir, agentId, sessions) {
  const agentDir = path.join(dir, agentId, 'sessions');
  fs.mkdirSync(agentDir, { recursive: true });
  fs.writeFileSync(path.join(agentDir, 'sessions.json'), JSON.stringify(sessions, null, 2));
  // Create stub transcript files so SessionMonitor's stale-check doesn't skip them
  for (const key of Object.keys(sessions)) {
    const entry = sessions[key]; // eslint-disable-line security/detect-object-injection
    const sessionId = entry.sessionId || entry.uuid;
    if (sessionId) {
      const transcriptPath = path.join(agentDir, `${sessionId}.jsonl`);
      if (!fs.existsSync(transcriptPath)) {
        fs.writeFileSync(transcriptPath, '');
      }
    }
  }
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

describe('SessionMonitor', () => {
  let tmpDir;
  let monitor;

  beforeEach(() => {
    tmpDir = createTmpDir();
  });

  afterEach(() => {
    if (monitor) {
      monitor.stop();
      monitor = null;
    }
    try {
      fs.rmSync(tmpDir, { recursive: true, force: true });
    } catch (_e) {
      /* ignore */
    }
  });

  describe('parseChannelId()', () => {
    it('parses channel ID from mattermost session key', () => {
      const key = 'agent:main:mattermost:channel:abc123:thread:xyz';
      assert.equal(SessionMonitor.parseChannelId(key), 'abc123');
    });

    it('parses DM channel', () => {
      const key = 'agent:main:mattermost:dm:user456';
      assert.equal(SessionMonitor.parseChannelId(key), 'user456');
    });

    it('returns null for non-MM session', () => {
      const key = 'agent:main:hook:session:xyz';
      assert.equal(SessionMonitor.parseChannelId(key), null);
    });
  });

  describe('parseRootPostId()', () => {
    it('parses thread ID from session key', () => {
      const key = 'agent:main:mattermost:channel:abc123:thread:rootpost999';
      assert.equal(SessionMonitor.parseRootPostId(key), 'rootpost999');
    });

    it('returns null if no thread', () => {
      const key = 'agent:main:mattermost:channel:abc123';
      assert.equal(SessionMonitor.parseRootPostId(key), null);
    });
  });

  describe('parseAgentId()', () => {
    it('extracts agent ID', () => {
      assert.equal(SessionMonitor.parseAgentId('agent:main:mattermost:channel:abc'), 'main');
      assert.equal(SessionMonitor.parseAgentId('agent:coder-agent:session'), 'coder-agent');
    });
  });

  describe('isMattermostSession()', () => {
    it('detects mattermost sessions', () => {
      assert.equal(SessionMonitor.isMattermostSession('agent:main:mattermost:channel:abc'), true);
      assert.equal(SessionMonitor.isMattermostSession('agent:main:hook:abc'), false);
    });
  });

  describe('session-added event', () => {
    it('emits session-added for new session in sessions.json', async () => {
      const sessionKey = 'agent:main:mattermost:channel:testchan:thread:testroot';
      const sessionId = 'aaaa-1111-bbbb-2222';

      writeSessionsJson(tmpDir, 'main', {
        [sessionKey]: {
          sessionId,
          spawnedBy: null,
          spawnDepth: 0,
          label: null,
          channel: 'mattermost',
        },
      });

      monitor = new SessionMonitor({
        transcriptDir: tmpDir,
        pollMs: 50,
      });

      const added = [];
      monitor.on('session-added', (info) => added.push(info));
      monitor.start();

      await sleep(200);

      assert.equal(added.length, 1);
      assert.equal(added[0].sessionKey, sessionKey);
      assert.equal(added[0].channelId, 'testchan');
      assert.equal(added[0].rootPostId, 'testroot');
      assert.ok(added[0].transcriptFile.endsWith(`${sessionId}.jsonl`));
    });

    it('emits session-removed when session disappears', async () => {
      const sessionKey = 'agent:main:mattermost:channel:testchan:thread:testroot';
      const sessionId = 'cccc-3333-dddd-4444';

      writeSessionsJson(tmpDir, 'main', {
        [sessionKey]: { sessionId, spawnedBy: null, spawnDepth: 0, channel: 'mattermost' },
      });

      monitor = new SessionMonitor({ transcriptDir: tmpDir, pollMs: 50 });

      const removed = [];
      monitor.on('session-removed', (key) => removed.push(key));
      monitor.start();

      await sleep(200);
      assert.equal(removed.length, 0);

      // Remove the session
      writeSessionsJson(tmpDir, 'main', {});

      await sleep(200);
      assert.equal(removed.length, 1);
      assert.equal(removed[0], sessionKey);
    });

    it('skips non-MM sessions with no default channel', async () => {
      const sessionKey = 'agent:main:hook:session:xyz';
      writeSessionsJson(tmpDir, 'main', {
        [sessionKey]: { sessionId: 'hook-uuid', channel: 'hook' },
      });

      monitor = new SessionMonitor({ transcriptDir: tmpDir, pollMs: 50, defaultChannel: null });

      const added = [];
      monitor.on('session-added', (info) => added.push(info));
      monitor.start();

      await sleep(200);
      assert.equal(added.length, 0);
    });

    it('includes non-MM sessions when defaultChannel is set', async () => {
      const sessionKey = 'agent:main:hook:session:xyz';
      writeSessionsJson(tmpDir, 'main', {
        [sessionKey]: { sessionId: 'hook-uuid', channel: 'hook' },
      });

      monitor = new SessionMonitor({
        transcriptDir: tmpDir,
        pollMs: 50,
        defaultChannel: 'default-channel-id',
      });

      const added = [];
      monitor.on('session-added', (info) => added.push(info));
      monitor.start();

      await sleep(200);
      assert.equal(added.length, 1);
      assert.equal(added[0].channelId, 'default-channel-id');
    });

    it('detects multiple agents', async () => {
      writeSessionsJson(tmpDir, 'main', {
        'agent:main:mattermost:channel:c1:thread:t1': {
          sessionId: 'sess-main',
          spawnedBy: null,
          channel: 'mattermost',
        },
      });
      writeSessionsJson(tmpDir, 'coder-agent', {
        'agent:coder-agent:mattermost:channel:c2:thread:t2': {
          sessionId: 'sess-coder',
          spawnedBy: null,
          channel: 'mattermost',
        },
      });

      monitor = new SessionMonitor({ transcriptDir: tmpDir, pollMs: 50 });

      const added = [];
      monitor.on('session-added', (info) => added.push(info));
      monitor.start();

      await sleep(200);
      assert.equal(added.length, 2);
      const keys = added.map((s) => s.sessionKey).sort();
      assert.ok(keys.includes('agent:main:mattermost:channel:c1:thread:t1'));
      assert.ok(keys.includes('agent:coder-agent:mattermost:channel:c2:thread:t2'));
    });

    it('handles malformed sessions.json gracefully', async () => {
      const agentDir = path.join(tmpDir, 'main', 'sessions');
      fs.mkdirSync(agentDir, { recursive: true });
      fs.writeFileSync(path.join(agentDir, 'sessions.json'), 'not valid json');

      monitor = new SessionMonitor({ transcriptDir: tmpDir, pollMs: 50 });

      const added = [];
      monitor.on('session-added', (info) => added.push(info));
      monitor.start();

      await sleep(200);
      // Should not throw and should produce no sessions
      assert.equal(added.length, 0);
    });
  });

  describe('getKnownSessions()', () => {
    it('returns current known sessions', async () => {
      const sessionKey = 'agent:main:mattermost:channel:c1:thread:t1';
      writeSessionsJson(tmpDir, 'main', {
        [sessionKey]: { sessionId: 'test-uuid', channel: 'mattermost' },
      });

      monitor = new SessionMonitor({ transcriptDir: tmpDir, pollMs: 50 });
      monitor.start();

      await sleep(200);

      const sessions = monitor.getKnownSessions();
      assert.equal(sessions.size, 1);
      assert.ok(sessions.has(sessionKey));
    });
  });
});
