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

mock.module('../../../src/services/domain/ModeManager.js', () => ({
  ModeManager: {
    getInstance: () => ({
      getActiveMode: () => ({
        name: 'code',
        prompts: {},
        observation_types: [
          { id: 'decision', icon: 'D' },
          { id: 'bugfix', icon: 'B' },
          { id: 'feature', icon: 'F' },
          { id: 'refactor', icon: 'R' },
          { id: 'discovery', icon: 'I' },
          { id: 'change', icon: 'C' }
        ],
        observation_concepts: [],
      }),
      getObservationTypes: () => [
        { id: 'decision', icon: 'D' },
        { id: 'bugfix', icon: 'B' },
        { id: 'feature', icon: 'F' },
        { id: 'refactor', icon: 'R' },
        { id: 'discovery', icon: 'I' },
        { id: 'change', icon: 'C' }
      ],
      getTypeIcon: (type: string) => {
        const icons: Record<string, string> = {
          decision: 'D',
          bugfix: 'B',
          feature: 'F',
          refactor: 'R',
          discovery: 'I',
          change: 'C'
        };
        return icons[type] || '?';
      },
      getWorkEmoji: () => 'W',
    }),
  },
}));

import { ResultFormatter } from '../../../src/services/worker/search/ResultFormatter.js';
import type { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult, SearchResults } from '../../../src/services/worker/search/types.js';

const mockObservation: ObservationSearchResult = {
  id: 1,
  memory_session_id: 'session-123',
  project: 'test-project',
  text: 'Test observation text',
  type: 'decision',
  title: 'Test Decision Title',
  subtitle: 'A descriptive subtitle',
  facts: '["fact1", "fact2"]',
  narrative: 'This is the narrative description',
  concepts: '["concept1", "concept2"]',
  files_read: '["src/file1.ts"]',
  files_modified: '["src/file2.ts"]',
  prompt_number: 1,
  discovery_tokens: 100,
  created_at: '2025-01-01T12:00:00.000Z',
  created_at_epoch: 1735732800000
};

const mockSession: SessionSummarySearchResult = {
  id: 1,
  memory_session_id: 'session-123',
  project: 'test-project',
  request: 'Implement feature X',
  investigated: 'Looked at code structure',
  learned: 'Learned about the architecture',
  completed: 'Added new feature',
  next_steps: 'Write tests',
  files_read: '["src/index.ts"]',
  files_edited: '["src/feature.ts"]',
  notes: 'Additional notes',
  prompt_number: 1,
  discovery_tokens: 500,
  created_at: '2025-01-01T12:00:00.000Z',
  created_at_epoch: 1735732800000
};

const mockPrompt: UserPromptSearchResult = {
  id: 1,
  content_session_id: 'content-123',
  prompt_number: 1,
  prompt_text: 'Can you help me implement feature X?',
  created_at: '2025-01-01T12:00:00.000Z',
  created_at_epoch: 1735732800000
};

describe('ResultFormatter', () => {
  let formatter: ResultFormatter;

  beforeEach(() => {
    formatter = new ResultFormatter();
  });

  describe('formatSearchResults', () => {
    it('should format observations as markdown', () => {
      const results: SearchResults = {
        observations: [mockObservation],
        sessions: [],
        prompts: []
      };

      const formatted = formatter.formatSearchResults(results, 'test query');

      expect(formatted).toContain('test query');
      expect(formatted).toContain('1 result');
      expect(formatted).toContain('1 obs');
      expect(formatted).toContain('#1'); 
      expect(formatted).toContain('Test Decision Title');
    });

    it('should format sessions as markdown', () => {
      const results: SearchResults = {
        observations: [],
        sessions: [mockSession],
        prompts: []
      };

      const formatted = formatter.formatSearchResults(results, 'session query');

      expect(formatted).toContain('1 session');
      expect(formatted).toContain('#S1'); 
      expect(formatted).toContain('Implement feature X');
    });

    it('should format prompts as markdown', () => {
      const results: SearchResults = {
        observations: [],
        sessions: [],
        prompts: [mockPrompt]
      };

      const formatted = formatter.formatSearchResults(results, 'prompt query');

      expect(formatted).toContain('1 prompt');
      expect(formatted).toContain('#P1'); 
      expect(formatted).toContain('Can you help me implement');
    });

    it('should handle empty results', () => {
      const results: SearchResults = {
        observations: [],
        sessions: [],
        prompts: []
      };

      const formatted = formatter.formatSearchResults(results, 'no matches');

      expect(formatted).toContain('No results found');
      expect(formatted).toContain('no matches');
    });

    it('should show combined count for multiple types', () => {
      const results: SearchResults = {
        observations: [mockObservation],
        sessions: [mockSession],
        prompts: [mockPrompt]
      };

      const formatted = formatter.formatSearchResults(results, 'mixed query');

      expect(formatted).toContain('3 result(s)');
      expect(formatted).toContain('1 obs');
      expect(formatted).toContain('1 sessions');
      expect(formatted).toContain('1 prompts');
    });

    it('should escape special characters in query', () => {
      const results: SearchResults = {
        observations: [mockObservation],
        sessions: [],
        prompts: []
      };

      const formatted = formatter.formatSearchResults(results, 'query with "quotes"');

      expect(formatted).toContain('query with "quotes"');
    });

    it('should include table headers', () => {
      const results: SearchResults = {
        observations: [mockObservation],
        sessions: [],
        prompts: []
      };

      const formatted = formatter.formatSearchResults(results, 'test');

      expect(formatted).toContain('| ID |');
      expect(formatted).toContain('| Time |');
      expect(formatted).toContain('| T |');
      expect(formatted).toContain('| Title |');
    });

    it('should indicate Chroma failure when chromaFailed is true', () => {
      const results: SearchResults = {
        observations: [],
        sessions: [],
        prompts: []
      };

      const formatted = formatter.formatSearchResults(results, 'test', true);

      expect(formatted).toContain('Semantic search failed');
      expect(formatted).toContain('Falling back to keyword search');
      expect(formatted).not.toContain('Install uv');
    });
  });

  describe('formatChromaFailureMessage', () => {
    it('formats connection-error reasons with offline messaging', () => {
      const message = ResultFormatter.formatChromaFailureMessage({
        message: 'subprocess closed',
        isConnectionError: true,
      });

      expect(message).toContain('Semantic search is offline');
      expect(message).toContain('Chroma MCP unreachable: subprocess closed');
      expect(message).toContain('Falling back to keyword search');
      expect(message).not.toContain('Install uv');
    });

    it('formats generic failure reasons with diagnostic pointer', () => {
      const message = ResultFormatter.formatChromaFailureMessage({
        message: 'something went wrong',
        isConnectionError: false,
      });

      expect(message).toContain('Semantic search failed: something went wrong');
      expect(message).toContain('Falling back to keyword search');
      expect(message).toContain('CHROMA_SYNC');
      expect(message).not.toContain('Install uv');
    });
  });

  describe('combineResults', () => {
    it('should combine all result types into unified format', () => {
      const results: SearchResults = {
        observations: [mockObservation],
        sessions: [mockSession],
        prompts: [mockPrompt]
      };

      const combined = formatter.combineResults(results);

      expect(combined).toHaveLength(3);
      expect(combined.some(r => r.type === 'observation')).toBe(true);
      expect(combined.some(r => r.type === 'session')).toBe(true);
      expect(combined.some(r => r.type === 'prompt')).toBe(true);
    });

    it('should include epoch for sorting', () => {
      const results: SearchResults = {
        observations: [mockObservation],
        sessions: [],
        prompts: []
      };

      const combined = formatter.combineResults(results);

      expect(combined[0].epoch).toBe(mockObservation.created_at_epoch);
    });

    it('should include created_at for display', () => {
      const results: SearchResults = {
        observations: [mockObservation],
        sessions: [],
        prompts: []
      };

      const combined = formatter.combineResults(results);

      expect(combined[0].created_at).toBe(mockObservation.created_at);
    });
  });

  describe('formatTableHeader', () => {
    it('should include Work column', () => {
      const header = formatter.formatTableHeader();

      expect(header).toContain('| Work |');
      expect(header).toContain('| ID |');
      expect(header).toContain('| Time |');
    });
  });

  describe('formatSearchTableHeader', () => {
    it('should not include Work column', () => {
      const header = formatter.formatSearchTableHeader();

      expect(header).not.toContain('| Work |');
      expect(header).toContain('| Read |');
    });
  });

  describe('formatObservationSearchRow', () => {
    it('should format observation as table row', () => {
      const result = formatter.formatObservationSearchRow(mockObservation, '');

      expect(result.row).toContain('#1');
      expect(result.row).toContain('Test Decision Title');
      expect(result.row).toContain('~'); 
    });

    it('should use quote mark for repeated time', () => {
      const firstResult = formatter.formatObservationSearchRow(mockObservation, '');
      const result = formatter.formatObservationSearchRow(mockObservation, firstResult.time);

      expect(result.row).toContain('"');
      expect(result.time).toBe(firstResult.time);
    });

    it('should return the time for tracking', () => {
      const result = formatter.formatObservationSearchRow(mockObservation, '');

      expect(typeof result.time).toBe('string');
    });
  });

  describe('formatSessionSearchRow', () => {
    it('should format session as table row', () => {
      const result = formatter.formatSessionSearchRow(mockSession, '');

      expect(result.row).toContain('#S1');
      expect(result.row).toContain('Implement feature X');
    });

    it('should fallback to session ID prefix when no request', () => {
      const sessionNoRequest = { ...mockSession, request: null };
      const result = formatter.formatSessionSearchRow(sessionNoRequest, '');

      expect(result.row).toContain('Session session-');
    });
  });

  describe('formatPromptSearchRow', () => {
    it('should format prompt as table row', () => {
      const result = formatter.formatPromptSearchRow(mockPrompt, '');

      expect(result.row).toContain('#P1');
      expect(result.row).toContain('Can you help me implement');
    });

    it('should truncate long prompts', () => {
      const longPrompt = {
        ...mockPrompt,
        prompt_text: 'A'.repeat(100)
      };

      const result = formatter.formatPromptSearchRow(longPrompt, '');

      expect(result.row).toContain('...');
      expect(result.row.length).toBeLessThan(longPrompt.prompt_text.length + 50);
    });
  });

  describe('formatObservationIndex', () => {
    it('should include Work column in index format', () => {
      const row = formatter.formatObservationIndex(mockObservation, 0);

      expect(row).toContain('#1');
      expect(row.split('|').length).toBeGreaterThan(5);
    });

    it('should show discovery tokens as work', () => {
      const obsWithTokens = { ...mockObservation, discovery_tokens: 250 };
      const row = formatter.formatObservationIndex(obsWithTokens, 0);

      expect(row).toContain('250');
    });

    it('should show dash when no discovery tokens', () => {
      const obsNoTokens = { ...mockObservation, discovery_tokens: 0 };
      const row = formatter.formatObservationIndex(obsNoTokens, 0);

      expect(row).toContain('-');
    });
  });

  describe('formatSessionIndex', () => {
    it('should include session ID prefix', () => {
      const row = formatter.formatSessionIndex(mockSession, 0);

      expect(row).toContain('#S1');
    });
  });

  describe('formatPromptIndex', () => {
    it('should include prompt ID prefix', () => {
      const row = formatter.formatPromptIndex(mockPrompt, 0);

      expect(row).toContain('#P1');
    });
  });

  describe('formatSearchTips', () => {
    it('should include search strategy tips', () => {
      const tips = formatter.formatSearchTips();

      expect(tips).toContain('Search Strategy');
      expect(tips).toContain('timeline');
      expect(tips).toContain('get_observations');
    });

    it('should include filter examples', () => {
      const tips = formatter.formatSearchTips();

      expect(tips).toContain('obs_type');
      expect(tips).toContain('dateStart');
      expect(tips).toContain('orderBy');
    });
  });
});
