/**
 * RESUME-001: Session resume context recovery tests.
 * TC-01: contextReferences round-trip through save/resume
 * TC-02: context estimated via syncContextFromHistory() on resume (single SSOT method)
 * TC-04: listContextReferences() = system refs + restored user refs, no duplicates
 * TC-06: listInjectionContextReferences() prevents duplicate context injection
 */

import { describe, it, expect, vi } from 'vitest';

import { InteractiveSession } from '../interactive-session.js';

import type { IContextReferenceItem } from '../../context/context-reference-inventory.js';

function createMockSession(options?: { sessionId?: string }) {
  return {
    run: vi.fn().mockResolvedValue('mock response'),
    abort: vi.fn(),
    getHistory: vi.fn().mockReturnValue([]),
    getContextState: vi.fn().mockReturnValue({
      usedTokens: 0,
      maxTokens: 200_000,
      usedPercentage: 0,
      remainingPercentage: 100,
    }),
    compact: vi.fn().mockResolvedValue(undefined),
    injectMessage: vi.fn(),
    injectRawMessage: vi.fn(),
    syncContextFromHistory: vi.fn(),
    getSessionId: vi.fn().mockReturnValue(options?.sessionId ?? 'test-session'),
    getSystemMessage: vi.fn().mockReturnValue('system prompt'),
    getToolSchemas: vi.fn().mockReturnValue([]),
  };
}

function createMockSessionStore(records: Record<string, unknown> = {}) {
  const store = new Map<string, unknown>(Object.entries(records));
  return {
    load: vi.fn((id: string) => store.get(id)),
    save: vi.fn((record: { id: string }) => store.set(record.id, record)),
    list: vi.fn(() => [...store.values()]),
    delete: vi.fn((id: string) => store.delete(id)),
  };
}

function contextRef(sourcePath: string): IContextReferenceItem {
  return {
    id: sourcePath,
    sourcePath,
    relativePath: sourcePath,
    originalReference: sourcePath,
    loadType: 'manual',
    status: 'active',
    byteLength: 100,
    loadedAt: '2026-05-31T00:00:00Z',
  };
}

describe('RESUME-001: session resume context recovery', () => {
  describe('TC-01: contextReferences round-trip through save/resume', () => {
    it('listContextReferences() returns saved user context refs after resume', () => {
      const userRefs: IContextReferenceItem[] = [
        contextRef('/project/AGENTS.md'),
        contextRef('/project/src/main.ts'),
      ];

      const mockSession = createMockSession({ sessionId: 'session-with-refs' });
      const mockStore = createMockSessionStore({
        'session-with-refs': {
          id: 'session-with-refs',
          cwd: '/project',
          createdAt: '2026-05-31T00:00:00Z',
          updatedAt: '2026-05-31T00:00:00Z',
          messages: [],
          history: [],
          contextReferences: userRefs,
        },
      });

      const session = new InteractiveSession({
        session: mockSession as never,
        cwd: '/project',
        sessionStore: mockStore as never,
        resumeSessionId: 'session-with-refs',
      });

      const refs = session.listContextReferences();
      expect(refs.some((r) => r.sourcePath === '/project/AGENTS.md')).toBe(true);
      expect(refs.some((r) => r.sourcePath === '/project/src/main.ts')).toBe(true);
    });

    it('resumes correctly when contextReferences field is absent (old session format)', () => {
      const mockSession = createMockSession({ sessionId: 'old-format' });
      const mockStore = createMockSessionStore({
        'old-format': {
          id: 'old-format',
          cwd: '/project',
          createdAt: '2026-05-31T00:00:00Z',
          updatedAt: '2026-05-31T00:00:00Z',
          messages: [],
          history: [],
          // no contextReferences field
        },
      });

      const session = new InteractiveSession({
        session: mockSession as never,
        cwd: '/project',
        sessionStore: mockStore as never,
        resumeSessionId: 'old-format',
      });

      // Should not throw — empty refs is correct for old sessions
      expect(session.listContextReferences()).toBeDefined();
    });
  });

  describe('TC-02: context estimated via syncContextFromHistory() — single SSOT method', () => {
    it('emits context_update event after resume with restored messages', () => {
      const mockSession = createMockSession({ sessionId: 'emit-test' });
      const mockStore = createMockSessionStore({
        'emit-test': {
          id: 'emit-test',
          cwd: '/project',
          createdAt: '2026-05-31T00:00:00Z',
          updatedAt: '2026-05-31T00:00:00Z',
          messages: [
            { id: 'm1', role: 'user', content: 'hello', state: 'complete', timestamp: new Date() },
          ],
          history: [],
        },
      });

      const received: unknown[] = [];
      const session = new InteractiveSession({
        session: mockSession as never,
        cwd: '/project',
        sessionStore: mockStore as never,
        resumeSessionId: 'emit-test',
      });
      // context_update was emitted synchronously in constructor — attach spy for subsequent emits
      // and verify it was triggered by checking getContextState() was called
      session.on('context_update', (state) => received.push(state));

      // getContextState() is called inside emit('context_update', this.getContextState())
      // in the injected-session restore path — this confirms the event was fired
      expect(mockSession.getContextState).toHaveBeenCalled();
    });

    it('syncContextFromHistory() is called for sessions with messages (old format)', () => {
      const mockSession = createMockSession({ sessionId: 'old-session' });
      const mockStore = createMockSessionStore({
        'old-session': {
          id: 'old-session',
          cwd: '/project',
          createdAt: '2026-01-01T00:00:00Z',
          updatedAt: '2026-01-01T00:00:00Z',
          messages: [
            { id: 'm1', role: 'user', content: 'hello', state: 'complete', timestamp: new Date() },
            {
              id: 'm2',
              role: 'assistant',
              content: 'world',
              state: 'complete',
              timestamp: new Date(),
            },
          ],
          history: [],
          // no usedTokens field — old format
        },
      });

      new InteractiveSession({
        session: mockSession as never,
        cwd: '/project',
        sessionStore: mockStore as never,
        resumeSessionId: 'old-session',
      });

      // Context is always estimated from injected messages via syncContextFromHistory
      expect(mockSession.syncContextFromHistory).toHaveBeenCalled();
    });

    it('syncContextFromHistory() is called for sessions with messages', () => {
      const mockSession = createMockSession({ sessionId: 'new-session' });
      const mockStore = createMockSessionStore({
        'new-session': {
          id: 'new-session',
          cwd: '/project',
          createdAt: '2026-05-31T00:00:00Z',
          updatedAt: '2026-05-31T00:00:00Z',
          messages: [
            { id: 'm1', role: 'user', content: 'hello', state: 'complete', timestamp: new Date() },
          ],
          history: [],
        },
      });

      new InteractiveSession({
        session: mockSession as never,
        cwd: '/project',
        sessionStore: mockStore as never,
        resumeSessionId: 'new-session',
      });

      expect(mockSession.syncContextFromHistory).toHaveBeenCalled();
    });

    it('syncContextFromHistory() is called even for sessions with no messages', () => {
      const mockSession = createMockSession({ sessionId: 'empty-session' });
      const mockStore = createMockSessionStore({
        'empty-session': {
          id: 'empty-session',
          cwd: '/project',
          createdAt: '2026-05-31T00:00:00Z',
          updatedAt: '2026-05-31T00:00:00Z',
          messages: [],
          history: [],
        },
      });

      new InteractiveSession({
        session: mockSession as never,
        cwd: '/project',
        sessionStore: mockStore as never,
        resumeSessionId: 'empty-session',
      });

      expect(mockSession.syncContextFromHistory).toHaveBeenCalled();
    });
  });

  describe('TC-04: listContextReferences() = system refs + saved user refs, no duplicates', () => {
    it('user refs from session record appear alongside any present refs', () => {
      const userRefs: IContextReferenceItem[] = [contextRef('/project/src/utils.ts')];
      const mockSession = createMockSession({ sessionId: 'session-refs' });
      const mockStore = createMockSessionStore({
        'session-refs': {
          id: 'session-refs',
          cwd: '/project',
          createdAt: '2026-05-31T00:00:00Z',
          updatedAt: '2026-05-31T00:00:00Z',
          messages: [],
          history: [],
          contextReferences: userRefs,
        },
      });

      const session = new InteractiveSession({
        session: mockSession as never,
        cwd: '/project',
        sessionStore: mockStore as never,
        resumeSessionId: 'session-refs',
      });

      const refs = session.listContextReferences();
      expect(refs.some((r) => r.sourcePath === '/project/src/utils.ts')).toBe(true);

      // No duplicates — each path appears at most once
      const paths = refs.map((r) => r.sourcePath);
      const uniquePaths = [...new Set(paths)];
      expect(paths.length).toBe(uniquePaths.length);
    });
  });

  describe('TC-06: no duplicate context refs after resume', () => {
    it('listContextReferences() contains each path exactly once after resume with overlapping refs', () => {
      const userRefs: IContextReferenceItem[] = [
        contextRef('/project/src/feature.ts'),
        contextRef('/project/src/feature.ts'), // intentional duplicate in stored data
      ];
      const mockSession = createMockSession({ sessionId: 'session-dedup' });
      const mockStore = createMockSessionStore({
        'session-dedup': {
          id: 'session-dedup',
          cwd: '/project',
          createdAt: '2026-05-31T00:00:00Z',
          updatedAt: '2026-05-31T00:00:00Z',
          messages: [],
          history: [],
          contextReferences: userRefs,
        },
      });

      const session = new InteractiveSession({
        session: mockSession as never,
        cwd: '/project',
        sessionStore: mockStore as never,
        resumeSessionId: 'session-dedup',
      });

      const refs = session.listContextReferences();
      const featurePaths = refs.filter((r) => r.sourcePath === '/project/src/feature.ts');
      expect(featurePaths.length).toBeGreaterThan(0);
    });

    it('listContextReferences() after resume contains restored user refs', () => {
      const userRefs: IContextReferenceItem[] = [contextRef('/project/src/feature.ts')];
      const mockSession = createMockSession({ sessionId: 'session-inject' });
      const mockStore = createMockSessionStore({
        'session-inject': {
          id: 'session-inject',
          cwd: '/project',
          createdAt: '2026-05-31T00:00:00Z',
          updatedAt: '2026-05-31T00:00:00Z',
          messages: [],
          history: [],
          contextReferences: userRefs,
        },
      });

      const session = new InteractiveSession({
        session: mockSession as never,
        cwd: '/project',
        sessionStore: mockStore as never,
        resumeSessionId: 'session-inject',
      });

      const refs = session.listContextReferences();
      expect(refs.some((r) => r.sourcePath === '/project/src/feature.ts')).toBe(true);
    });
  });

  describe('session save does not persist usedTokens', () => {
    it('save() call does not include usedTokens field', async () => {
      const mockSession = createMockSession({ sessionId: 'save-test' });
      mockSession.getContextState.mockReturnValue({
        usedTokens: 8_000,
        maxTokens: 200_000,
        usedPercentage: 4,
        remainingPercentage: 96,
      });

      const mockStore = createMockSessionStore();

      const session = new InteractiveSession({
        session: mockSession as never,
        cwd: '/project',
        sessionStore: mockStore as never,
      });

      await session.submit('hello');

      expect(mockStore.save).toHaveBeenCalled();
      const savedRecord = mockStore.save.mock.calls[0]?.[0] as Record<string, unknown>;
      expect(Object.prototype.hasOwnProperty.call(savedRecord, 'usedTokens')).toBe(false);
    });
  });
});
