import 'reflect-metadata';
import { container } from 'tsyringe';
import { EntityExtractorService } from '../../../src/services/nlp/extractors/entity-extractor.service';
import { OpenAIService } from '../../../src/services/ai/openai.service';
import { DateTime } from 'luxon';

// Mock OpenAIService
jest.mock('../../../src/services/ai/openai.service');

describe('EntityExtractorService', () => {
  let service: EntityExtractorService;
  let mockOpenAIService: jest.Mocked<OpenAIService>;

  beforeEach(() => {
    jest.clearAllMocks();
    container.clearInstances();

    // Create mock OpenAI service
    mockOpenAIService = {
      complete: jest.fn(),
    } as any;

    // Register mock with container
    container.registerInstance(OpenAIService, mockOpenAIService);

    // Create service
    service = container.resolve(EntityExtractorService);
  });

  describe('extract', () => {
    it('should extract task title and description', async () => {
      // Arrange
      const text = 'Create a user authentication system with OAuth support';
      const parsedData = {
        tokens: ['Create', 'a', 'user', 'authentication', 'system'],
        sentences: [text],
      };

      mockOpenAIService.complete.mockResolvedValue({
        content: JSON.stringify({
          title: 'User authentication system',
          description: 'Implement user authentication with OAuth support',
          taskType: 'feature',
        }),
        usage: { totalTokens: 100 },
      });

      // Act
      const result = await service.extract({ text, parsedData, language: 'en' });

      // Assert
      expect(result.title).toBe('User authentication system');
      expect(result.description).toContain('OAuth support');
      expect(mockOpenAIService.complete).toHaveBeenCalled();
    });

    it('should extract priority from contextual cues', async () => {
      // Arrange
      const text = 'URGENT: Fix the critical security vulnerability immediately';
      const parsedData = {
        tokens: ['URGENT', 'Fix', 'the', 'critical', 'security', 'vulnerability'],
        sentences: [text],
      };

      mockOpenAIService.complete.mockResolvedValue({
        content: JSON.stringify({
          title: 'Fix security vulnerability',
          priority: 'critical',
          urgency: 'immediate',
        }),
        usage: { totalTokens: 100 },
      });

      // Act
      const result = await service.extract({ text, parsedData, language: 'en' });

      // Assert
      expect(result.priority).toBe('critical');
    });

    it('should extract deadline from various date formats', async () => {
      // Arrange
      const text = 'Complete the report by next Friday at 5 PM';
      const parsedData = {
        tokens: ['Complete', 'the', 'report', 'by', 'next', 'Friday'],
        sentences: [text],
      };

      const nextFriday = DateTime.now().plus({ weeks: 1 }).set({ weekday: 5, hour: 17, minute: 0 });

      mockOpenAIService.complete.mockResolvedValue({
        content: JSON.stringify({
          title: 'Complete the report',
          deadline: 'next Friday at 5 PM',
          parsedDeadline: nextFriday.toISO(),
        }),
        usage: { totalTokens: 100 },
      });

      // Act
      const result = await service.extract({ text, parsedData, language: 'en' });

      // Assert
      expect(result.deadline).toBeDefined();
      expect(result.deadline).toContain('Friday');
    });

    it('should extract assignees and mentions', async () => {
      // Arrange
      const text = 'John and Sarah should review the code, cc @mike';
      const parsedData = {
        tokens: ['John', 'and', 'Sarah', 'should', 'review'],
        sentences: [text],
      };

      mockOpenAIService.complete.mockResolvedValue({
        content: JSON.stringify({
          title: 'Code review',
          assignees: ['John', 'Sarah'],
          mentions: ['mike'],
        }),
        usage: { totalTokens: 100 },
      });

      // Act
      const result = await service.extract({ text, parsedData, language: 'en' });

      // Assert
      expect(result.assignees).toEqual(['John', 'Sarah']);
      expect(result.mentions).toContain('mike');
    });

    it('should extract dependencies and blockers', async () => {
      // Arrange
      const text = 'After completing task #123, start the API integration. Blocked by database migration.';
      const parsedData = {
        tokens: ['After', 'completing', 'task', '#123'],
        sentences: [text],
      };

      mockOpenAIService.complete.mockResolvedValue({
        content: JSON.stringify({
          title: 'API integration',
          dependencies: ['task-123'],
          blockers: ['database migration'],
        }),
        usage: { totalTokens: 100 },
      });

      // Act
      const result = await service.extract({ text, parsedData, language: 'en' });

      // Assert
      expect(result.dependencies).toContain('task-123');
      expect(result.blockers).toContain('database migration');
    });

    it('should extract estimated hours and story points', async () => {
      // Arrange
      const text = 'This task should take about 8 hours or 3 story points';
      const parsedData = {
        tokens: ['This', 'task', 'should', 'take', 'about', '8', 'hours'],
        sentences: [text],
      };

      mockOpenAIService.complete.mockResolvedValue({
        content: JSON.stringify({
          title: 'Task',
          estimatedHours: 8,
          storyPoints: 3,
        }),
        usage: { totalTokens: 100 },
      });

      // Act
      const result = await service.extract({ text, parsedData, language: 'en' });

      // Assert
      expect(result.estimatedHours).toBe(8);
      expect(result.storyPoints).toBe(3);
    });

    it('should extract tags and labels', async () => {
      // Arrange
      const text = 'Add authentication feature #backend #security #api';
      const parsedData = {
        tokens: ['Add', 'authentication', 'feature', '#backend', '#security', '#api'],
        sentences: [text],
      };

      mockOpenAIService.complete.mockResolvedValue({
        content: JSON.stringify({
          title: 'Add authentication feature',
          tags: ['backend', 'security', 'api'],
        }),
        usage: { totalTokens: 100 },
      });

      // Act
      const result = await service.extract({ text, parsedData, language: 'en' });

      // Assert
      expect(result.tags).toEqual(['backend', 'security', 'api']);
    });

    it('should handle multi-language input', async () => {
      // Arrange
      const text = 'Créer une tâche urgente'; // French
      const parsedData = {
        tokens: ['Créer', 'une', 'tâche', 'urgente'],
        sentences: [text],
      };

      mockOpenAIService.complete.mockResolvedValue({
        content: JSON.stringify({
          title: 'Tâche urgente',
          priority: 'high',
          language: 'fr',
        }),
        usage: { totalTokens: 100 },
      });

      // Act
      const result = await service.extractEntities(text, parsedData, 'fr');

      // Assert
      expect(result.title).toBe('Tâche urgente');
      expect(result.priority).toBe('high');
    });

    it('should provide confidence scores', async () => {
      // Arrange
      const text = 'Maybe create something related to users';
      const parsedData = {
        tokens: ['Maybe', 'create', 'something'],
        sentences: [text],
      };

      mockOpenAIService.complete.mockResolvedValue({
        content: JSON.stringify({
          title: 'User-related task',
          confidence: 0.6,
        }),
        usage: { totalTokens: 100 },
      });

      // Act
      const result = await service.extract({ text, parsedData, language: 'en' });

      // Assert
      expect(result.confidence).toBeLessThan(0.7);
    });

    it('should handle errors gracefully', async () => {
      // Arrange
      const text = 'Test text';
      const parsedData = { tokens: ['Test', 'text'], sentences: [text] };

      mockOpenAIService.complete.mockRejectedValue(new Error('API error'));

      // Act & Assert
      await expect(service.extract({ text, parsedData, language: 'en' })).rejects.toThrow('API error');
    });
  });

  describe('pattern-based extraction', () => {
    it('should extract mentions using pattern matching', async () => {
      // Arrange
      const text = 'Assign to @john.doe and notify @sarah_smith';
      const parsedData = {
        tokens: ['Assign', 'to', '@john.doe', 'and', 'notify', '@sarah_smith'],
        sentences: [text],
      };

      // Don't mock OpenAI to test pattern matching
      mockOpenAIService.complete.mockResolvedValue({
        content: JSON.stringify({
          title: 'Task',
          mentions: ['john.doe', 'sarah_smith'],
        }),
        usage: { totalTokens: 100 },
      });

      // Act
      const result = await service.extract({ text, parsedData, language: 'en' });

      // Assert
      expect(result.mentions).toContain('john.doe');
      expect(result.mentions).toContain('sarah_smith');
    });

    it('should extract task IDs using pattern matching', async () => {
      // Arrange
      const text = 'Related to #TASK-123 and fixes #BUG-456';
      const parsedData = {
        tokens: ['Related', 'to', '#TASK-123', 'and', 'fixes', '#BUG-456'],
        sentences: [text],
      };

      mockOpenAIService.complete.mockResolvedValue({
        content: JSON.stringify({
          title: 'Task',
          relatedTasks: ['TASK-123', 'BUG-456'],
        }),
        usage: { totalTokens: 100 },
      });

      // Act
      const result = await service.extract({ text, parsedData, language: 'en' });

      // Assert
      expect(result.relatedTasks).toContain('TASK-123');
      expect(result.relatedTasks).toContain('BUG-456');
    });
  });
});