import { describe, it, expect, beforeEach, mock, spyOn } from 'bun:test';

describe('Stale AbortController Guard (#1099)', () => {
  describe('ActiveSession.lastGeneratorActivity', () => {
    it('should be defined in ActiveSession type', () => {
      const session = {
        sessionDbId: 1,
        contentSessionId: 'test',
        memorySessionId: null,
        project: 'test',
        userPrompt: 'test',
        pendingMessages: [],
        abortController: new AbortController(),
        generatorPromise: null,
        lastPromptNumber: 1,
        startTime: Date.now(),
        cumulativeInputTokens: 0,
        cumulativeOutputTokens: 0,
        earliestPendingTimestamp: null,
        claimedMessageIds: [],
        conversationHistory: [],
        currentProvider: null,
        consecutiveRestarts: 0,
        lastGeneratorActivity: Date.now()
      };

      expect(session.lastGeneratorActivity).toBeGreaterThan(0);
    });

    it('should update when set to current time', () => {
      const before = Date.now();
      const activity = Date.now();
      expect(activity).toBeGreaterThanOrEqual(before);
    });
  });

  describe('Stale generator detection logic', () => {
    const STALE_THRESHOLD_MS = 30_000;

    it('should detect generator as stale when no activity for >30s', () => {
      const lastActivity = Date.now() - 31_000; 
      const timeSinceActivity = Date.now() - lastActivity;
      expect(timeSinceActivity).toBeGreaterThan(STALE_THRESHOLD_MS);
    });

    it('should NOT detect generator as stale when activity within 30s', () => {
      const lastActivity = Date.now() - 5_000; 
      const timeSinceActivity = Date.now() - lastActivity;
      expect(timeSinceActivity).toBeLessThan(STALE_THRESHOLD_MS);
    });

    it('should reset activity timestamp when generator restarts', () => {
      const session = {
        lastGeneratorActivity: Date.now() - 60_000, // 60 seconds ago (stale)
        abortController: new AbortController(),
        generatorPromise: Promise.resolve() as Promise<void> | null,
      };

      session.abortController.abort();
      session.generatorPromise = null;
      session.abortController = new AbortController();
      session.lastGeneratorActivity = Date.now();

      const timeSinceActivity = Date.now() - session.lastGeneratorActivity;
      expect(timeSinceActivity).toBeLessThan(STALE_THRESHOLD_MS);
      expect(session.abortController.signal.aborted).toBe(false);
    });
  });

  describe('AbortSignal.timeout for deleteSession', () => {
    it('should resolve timeout signal after specified ms', async () => {
      const start = Date.now();
      const timeoutMs = 50; 

      await new Promise<void>(resolve => {
        AbortSignal.timeout(timeoutMs).addEventListener('abort', () => resolve(), { once: true });
      });

      const elapsed = Date.now() - start;
      expect(elapsed).toBeGreaterThanOrEqual(timeoutMs - 10);
    });

    it('should race generator promise against timeout', async () => {
      const hungGenerator = new Promise<void>(() => {});
      const timeoutMs = 50;

      const timeoutDone = new Promise<string>(resolve => {
        AbortSignal.timeout(timeoutMs).addEventListener('abort', () => resolve('timeout'), { once: true });
      });

      const generatorDone = hungGenerator.then(() => 'generator');

      const result = await Promise.race([generatorDone, timeoutDone]);
      expect(result).toBe('timeout');
    });

    it('should prefer generator completion over timeout when fast', async () => {
      const fastGenerator = Promise.resolve('generator');
      const timeoutMs = 5000;

      const timeoutDone = new Promise<string>(resolve => {
        AbortSignal.timeout(timeoutMs).addEventListener('abort', () => resolve('timeout'), { once: true });
      });

      const result = await Promise.race([fastGenerator, timeoutDone]);
      expect(result).toBe('generator');
    });
  });

  describe('AbortController replacement on stale recovery', () => {
    it('should create fresh AbortController that is not aborted', () => {
      const oldController = new AbortController();
      oldController.abort();
      expect(oldController.signal.aborted).toBe(true);

      const newController = new AbortController();
      expect(newController.signal.aborted).toBe(false);
    });

    it('should not affect new controller when old is aborted', () => {
      const oldController = new AbortController();
      const newController = new AbortController();

      oldController.abort();

      expect(oldController.signal.aborted).toBe(true);
      expect(newController.signal.aborted).toBe(false);
    });
  });
});
