import 'reflect-metadata';
import request from 'supertest';
import express, { Application } from 'express';
import { container } from 'tsyringe';
import createNLPRoutes from '../../src/api/routes/nlp.routes';
import { NLPService } from '../../src/services/nlp/nlp.service';
import { TranslationService } from '../../src/services/nlp/translation.service';
import { LanguageDetectorService } from '../../src/services/nlp/language-detector.service';
import { authMiddleware } from '../../src/api/middleware/auth.middleware';

// Mock services
jest.mock('../../src/services/nlp/nlp.service');
jest.mock('../../src/services/nlp/translation.service');
jest.mock('../../src/services/nlp/language-detector.service');
jest.mock('../../src/api/middleware/auth.middleware', () => ({
  authMiddleware: jest.fn((req, res, next) => {
    req.user = {
      id: 'test-user',
      email: 'test@example.com',
      role: 'ADMIN',
      permissions: ['CREATE_TASK', 'UPDATE_TASK'],
    };
    next();
  }),
}));

describe('NLP API Integration Tests', () => {
  let app: Application;
  let mockNLPService: jest.Mocked<NLPService>;
  let mockTranslationService: jest.Mocked<TranslationService>;
  let mockLanguageDetector: jest.Mocked<LanguageDetectorService>;

  beforeEach(() => {
    // Clear all mocks
    jest.clearAllMocks();
    container.clearInstances();

    // Create Express app
    app = express();
    app.use(express.json());

    // Create mock services
    mockNLPService = {
      processText: jest.fn(),
      processTextWithTranslation: jest.fn(),
      getCacheStats: jest.fn(),
      clearCache: jest.fn(),
    } as any;

    mockTranslationService = {
      translate: jest.fn(),
      getCacheStats: jest.fn(),
      clearCache: jest.fn(),
    } as any;

    mockLanguageDetector = {
      detectLanguage: jest.fn(),
      getSupportedLanguages: jest.fn(),
    } as any;

    // Register mocks with container
    container.registerInstance(NLPService, mockNLPService);
    container.registerInstance(TranslationService, mockTranslationService);
    container.registerInstance(LanguageDetectorService, mockLanguageDetector);

    // Setup routes
    app.use('/api/nlp', createNLPRoutes());
  });

  describe('POST /api/nlp/parse', () => {
    it('should parse natural language text successfully', async () => {
      // Arrange
      const mockResult: any = {
        success: true,
        entities: {
          title: 'Implement user authentication',
          priority: 'high',
          deadline: '2025-01-20',
        },
        classification: {
          taskType: 'feature',
          urgency: 'medium',
        },
        structuredData: {
          title: 'Implement user authentication',
          description: 'Add user authentication feature',
        },
        language: 'en',
        processingTime: 150,
        tokensUsed: 200,
        errors: [],
      };

      mockNLPService.processTextWithTranslation.mockResolvedValue(mockResult);

      // Act
      const response = await request(app)
        .post('/api/nlp/parse')
        .set('Authorization', 'Bearer test-token')
        .send({
          text: 'Create a high-priority task to implement user authentication by Monday',
          language: 'en',
        });

      // Assert
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.entities.title).toBe('Implement user authentication');
      expect(response.body.data.classification.taskType).toBe('feature');
      expect(mockNLPService.processTextWithTranslation).toHaveBeenCalledWith({
        text: 'Create a high-priority task to implement user authentication by Monday',
        language: 'en',
        projectId: undefined,
        userId: undefined,
        context: undefined,
      });
    });

    it('should handle validation errors', async () => {
      // Act
      const response = await request(app)
        .post('/api/nlp/parse')
        .set('Authorization', 'Bearer test-token')
        .send({
          // Missing required 'text' field
          language: 'en',
        });

      // Assert
      expect(response.status).toBe(400);
      expect(response.body.success).toBe(false);
      expect(response.body.error).toBe('Validation Error');
      expect(response.body.details).toBeDefined();
    });

    it('should handle service errors', async () => {
      // Arrange
      mockNLPService.processTextWithTranslation.mockRejectedValue(
        new Error('Processing failed')
      );

      // Act
      const response = await request(app)
        .post('/api/nlp/parse')
        .set('Authorization', 'Bearer test-token')
        .send({
          text: 'Test text',
        });

      // Assert
      expect(response.status).toBe(500);
      expect(response.body.success).toBe(false);
      expect(response.body.error).toBe('Failed to parse text');
      expect(response.body.message).toBe('Processing failed');
    });

    it('should include optional context in processing', async () => {
      // Arrange
      const mockResult: any = {
        success: true,
        entities: { title: 'Task' },
        classification: { taskType: 'task' },
        structuredData: { title: 'Task' },
        language: 'en',
        processingTime: 100,
        tokensUsed: 150,
        errors: [],
      };

      mockNLPService.processTextWithTranslation.mockResolvedValue(mockResult);

      // Act
      const response = await request(app)
        .post('/api/nlp/parse')
        .set('Authorization', 'Bearer test-token')
        .send({
          text: 'Create task',
          projectId: 'project-123',
          userId: 'user-456',
          context: { custom: 'data' },
        });

      // Assert
      expect(response.status).toBe(200);
      expect(mockNLPService.processTextWithTranslation).toHaveBeenCalledWith({
        text: 'Create task',
        language: undefined,
        projectId: 'project-123',
        userId: 'user-456',
        context: { custom: 'data' },
      });
    });
  });

  describe('POST /api/nlp/extract-entities', () => {
    it('should extract entities from text', async () => {
      // Arrange
      const mockResult: any = {
        success: true,
        entities: {
          title: 'Code review',
          assignees: ['John', 'Sarah'],
          deadline: '2025-01-18',
          confidence: 0.9,
        },
        classification: {},
        structuredData: {},
        language: 'en',
        processingTime: 120,
      };

      mockNLPService.processText.mockResolvedValue(mockResult);

      // Act
      const response = await request(app)
        .post('/api/nlp/extract-entities')
        .set('Authorization', 'Bearer test-token')
        .send({
          text: 'John and Sarah should review the code by Friday',
        });

      // Assert
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.entities.assignees).toEqual(['John', 'Sarah']);
      expect(response.body.metadata.confidence).toBe(0.9);
    });
  });

  describe('POST /api/nlp/classify', () => {
    it('should classify task type and attributes', async () => {
      // Arrange
      const mockResult: any = {
        success: true,
        entities: {},
        classification: {
          taskType: 'bug',
          priority: 'high',
          severity: 'critical',
          confidence: 0.95,
        },
        structuredData: {},
        language: 'en',
        processingTime: 100,
      };

      mockNLPService.processText.mockResolvedValue(mockResult);

      // Act
      const response = await request(app)
        .post('/api/nlp/classify')
        .set('Authorization', 'Bearer test-token')
        .send({
          text: 'Fix critical bug in payment processing',
          entities: {
            title: 'Fix payment bug',
            priority: 'high',
          },
        });

      // Assert
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.classification.taskType).toBe('bug');
      expect(response.body.data.classification.severity).toBe('critical');
      expect(response.body.metadata.confidence).toBe(0.95);
    });
  });

  describe('POST /api/nlp/translate', () => {
    it('should translate text between languages', async () => {
      // Arrange
      const mockResult: any = {
        originalText: 'Créer une nouvelle tâche',
        translatedText: 'Create a new task',
        sourceLanguage: 'fr',
        targetLanguage: 'en',
        confidence: 0.92,
        alternatives: ['Make a new task', 'Add a new task'],
      };

      mockTranslationService.translate.mockResolvedValue(mockResult);

      // Act
      const response = await request(app)
        .post('/api/nlp/translate')
        .set('Authorization', 'Bearer test-token')
        .send({
          text: 'Créer une nouvelle tâche',
          targetLanguage: 'en',
        });

      // Assert
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.translatedText).toBe('Create a new task');
      expect(response.body.data.sourceLanguage).toBe('fr');
      expect(response.body.metadata.alternatives).toContain('Make a new task');
    });

    it('should validate required target language', async () => {
      // Act
      const response = await request(app)
        .post('/api/nlp/translate')
        .set('Authorization', 'Bearer test-token')
        .send({
          text: 'Test text',
          // Missing targetLanguage
        });

      // Assert
      expect(response.status).toBe(400);
      expect(response.body.success).toBe(false);
      expect(response.body.error).toBe('Validation Error');
    });
  });

  describe('POST /api/nlp/detect-language', () => {
    it('should detect language of input text', async () => {
      // Arrange
      const mockResult: any = {
        language: 'es',
        confidence: 0.98,
        alternatives: [
          { language: 'pt', confidence: 0.15 },
          { language: 'it', confidence: 0.08 },
        ],
      };

      mockLanguageDetector.detectLanguage.mockResolvedValue(mockResult);

      // Act
      const response = await request(app)
        .post('/api/nlp/detect-language')
        .set('Authorization', 'Bearer test-token')
        .send({
          text: 'Necesito crear una tarea urgente',
        });

      // Assert
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.language).toBe('es');
      expect(response.body.data.confidence).toBe(0.98);
      expect(response.body.metadata.alternatives).toHaveLength(2);
    });
  });

  describe('GET /api/nlp/languages', () => {
    it('should return supported languages', async () => {
      // Arrange
      const mockLanguages: any = [
        { code: 'en', name: 'English' },
        { code: 'es', name: 'Spanish' },
        { code: 'fr', name: 'French' },
        { code: 'de', name: 'German' },
        { code: 'pt', name: 'Portuguese' },
        { code: 'it', name: 'Italian' },
        { code: 'zh', name: 'Chinese' },
        { code: 'ja', name: 'Japanese' },
      ];

      mockLanguageDetector.getSupportedLanguages.mockReturnValue(mockLanguages);

      // Act
      const response = await request(app)
        .get('/api/nlp/languages')
        .set('Authorization', 'Bearer test-token');

      // Assert
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.languages).toHaveLength(8);
      expect(response.body.data.languages[0].code).toBe('en');
    });
  });

  describe('GET /api/nlp/cache/stats', () => {
    it('should return cache statistics', async () => {
      // Arrange
      const mockNLPStats: any = {
        size: 25,
        maxAge: 3600000,
      };

      const mockTranslationStats: any = {
        size: 15,
        maxAge: 3600000,
      };

      mockNLPService.getCacheStats.mockReturnValue(mockNLPStats);
      mockTranslationService.getCacheStats.mockReturnValue(mockTranslationStats);

      // Act
      const response = await request(app)
        .get('/api/nlp/cache/stats')
        .set('Authorization', 'Bearer test-token');

      // Assert
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.nlpCache.size).toBe(25);
      expect(response.body.data.translationCache.size).toBe(15);
    });
  });

  describe('DELETE /api/nlp/cache', () => {
    it('should clear NLP caches', async () => {
      // Act
      const response = await request(app)
        .delete('/api/nlp/cache')
        .set('Authorization', 'Bearer test-token');

      // Assert
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.message).toBe('Cache cleared successfully');
      expect(mockNLPService.clearCache).toHaveBeenCalled();
      expect(mockTranslationService.clearCache).toHaveBeenCalled();
    });
  });

  describe('Rate Limiting', () => {
    it('should apply rate limiting to NLP endpoints', async () => {
      // Note: This would require mocking the rate limiter
      // For now, we'll just verify the endpoint accepts the rate limit header
      const response = await request(app)
        .post('/api/nlp/parse')
        .set('Authorization', 'Bearer test-token')
        .set('X-RateLimit-Limit', '100')
        .send({
          text: 'Test rate limiting',
        });

      // Just verify the endpoint is accessible
      expect([200, 429, 500]).toContain(response.status);
    });
  });

  describe('Authentication', () => {
    it('should require authentication for all endpoints', async () => {
      // Remove auth middleware mock for this test
      jest.resetModules();
      
      // This test would verify that without proper auth, requests are rejected
      // For now, we verify the middleware is called
      expect(authMiddleware).toBeDefined();
    });
  });
});