import 'reflect-metadata';
import request from 'supertest';
import express, { Express } from 'express';

// Mock services
const mockAITaskService = {
  createTaskFromNaturalLanguage: jest.fn(),
  updateTaskContext: jest.fn(),
  getTaskSuggestions: jest.fn(),
  breakdownTask: jest.fn(),
  getHealthStatus: jest.fn(),
  getUsageStats: jest.fn(),
  warmCache: jest.fn(),
};

const mockOpenAIService = {
  testConnection: jest.fn(),
};

const mockLangChainService = {
  testConnection: jest.fn(),
};

// Mock DI container before importing routes
jest.mock('tsyringe', () => ({
  container: {
    resolve: jest.fn((token: string) => {
      if (token === 'AITaskService') return mockAITaskService;
      if (token === 'OpenAIService') return mockOpenAIService;
      if (token === 'LangChainService') return mockLangChainService;
      return null;
    }),
    register: jest.fn(),
  },
  injectable: jest.fn(() => (target: any) => target),
  inject: jest.fn(() => (target: any, key: any, index: any) => {}),
}));

// Import routes after mocking
const aiRoutes = require('../../src/api/routes/ai.routes').default;

describe('AI API Integration Tests', () => {
  let app: Express;

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

    // Create Express app
    app = express();
    app.use(express.json());
    app.use('/api/ai', aiRoutes);

    // Add error handler
    app.use((err: any, req: any, res: any, next: any) => {
      res.status(err.status || 500).json({
        success: false,
        error: err.message || 'Internal server error',
      });
    });
  });

  describe('POST /api/ai/create-task', () => {
    it('should create a task from natural language successfully', async () => {
      const mockTask = {
        title: 'Implement user authentication',
        description: 'Add JWT-based authentication to the application',
        priority: 'high',
        complexity: 'major',
        estimatedHours: 16,
        acceptanceCriteria: [
          'Users can register',
          'Users can login',
          'JWT tokens are secure',
        ],
        tags: ['auth', 'security'],
      };

      mockAITaskService.createTaskFromNaturalLanguage.mockResolvedValue(mockTask);

      const response = await request(app)
        .post('/api/ai/create-task')
        .send({
          prompt: 'Implement user authentication with JWT',
          projectId: 'project-123',
          context: {
            projectDescription: 'E-commerce platform',
            currentTasks: ['Database setup', 'API structure'],
          },
        })
        .expect(201);

      expect(response.body).toEqual({
        success: true,
        data: mockTask,
        message: 'Task created successfully from natural language',
      });

      expect(mockAITaskService.createTaskFromNaturalLanguage).toHaveBeenCalledWith({
        prompt: 'Implement user authentication with JWT',
        projectId: 'project-123',
        context: {
          projectDescription: 'E-commerce platform',
          currentTasks: ['Database setup', 'API structure'],
        },
      });
    });

    it('should return 400 for invalid request', async () => {
      const response = await request(app)
        .post('/api/ai/create-task')
        .send({
          // Missing required 'prompt' field
          projectId: 'project-123',
        })
        .expect(400);

      expect(response.body).toMatchObject({
        success: false,
        error: 'Validation error',
        details: expect.any(Array),
      });

      expect(mockAITaskService.createTaskFromNaturalLanguage).not.toHaveBeenCalled();
    });

    it('should handle rate limiting', async () => {
      mockAITaskService.createTaskFromNaturalLanguage.mockRejectedValue(
        new Error('Rate limit exceeded for task_creation')
      );

      const response = await request(app)
        .post('/api/ai/create-task')
        .send({
          prompt: 'Create a task',
        })
        .expect(429);

      expect(response.body).toEqual({
        success: false,
        error: 'Rate limit exceeded',
        message: 'Too many requests. Please try again later.',
      });
    });
  });

  describe('PUT /api/ai/update-context/:taskId', () => {
    it('should update task context with feedback', async () => {
      mockAITaskService.updateTaskContext.mockResolvedValue(undefined);

      const response = await request(app)
        .put('/api/ai/update-context/task-123')
        .send({
          feedback: 'The task needs more specific acceptance criteria',
        })
        .expect(200);

      expect(response.body).toEqual({
        success: true,
        message: 'Task context updated successfully',
      });

      expect(mockAITaskService.updateTaskContext).toHaveBeenCalledWith(
        'task-123',
        'The task needs more specific acceptance criteria'
      );
    });

    it('should validate feedback is provided', async () => {
      const response = await request(app)
        .put('/api/ai/update-context/task-123')
        .send({})
        .expect(400);

      expect(response.body).toMatchObject({
        success: false,
        error: 'Validation error',
      });
    });
  });

  describe('GET /api/ai/suggestions/:projectId', () => {
    it('should get task suggestions for a project', async () => {
      const mockSuggestions = [
        {
          id: 'suggestion-1',
          title: 'Add error handling',
          description: 'Implement comprehensive error handling',
          rationale: 'Improves user experience and debugging',
          priority: 'medium',
          confidence: 0.85,
        },
        {
          id: 'suggestion-2',
          title: 'Setup logging',
          description: 'Add structured logging system',
          rationale: 'Essential for production monitoring',
          priority: 'high',
          confidence: 0.92,
        },
      ];

      mockAITaskService.getTaskSuggestions.mockResolvedValue(mockSuggestions);

      const response = await request(app)
        .get('/api/ai/suggestions/project-123')
        .query({
          projectDescription: 'E-commerce platform',
          completedTasks: 'Setup database,Create API structure',
          currentTasks: 'Build UI components',
          goals: 'Launch MVP,Scale to 1000 users',
        })
        .expect(200);

      expect(response.body).toEqual({
        success: true,
        data: mockSuggestions,
        projectId: 'project-123',
      });

      expect(mockAITaskService.getTaskSuggestions).toHaveBeenCalledWith(
        'project-123',
        {
          projectDescription: 'E-commerce platform',
          completedTasks: ['Setup database', 'Create API structure'],
          currentTasks: ['Build UI components'],
          goals: ['Launch MVP', 'Scale to 1000 users'],
        }
      );
    });

    it('should handle missing query parameters', async () => {
      const response = await request(app)
        .get('/api/ai/suggestions/project-123')
        .expect(400);

      expect(response.body).toMatchObject({
        success: false,
        error: 'Validation error',
      });
    });
  });

  describe('POST /api/ai/breakdown/:taskId', () => {
    it('should break down a task into subtasks', async () => {
      const mockBreakdown = {
        title: 'Implement authentication',
        description: 'Create secure authentication system',
        subtasks: [
          {
            id: '1',
            title: 'Setup database schema',
            description: 'Create user tables and indexes',
            estimatedHours: 2,
          },
          {
            id: '2',
            title: 'Implement JWT logic',
            description: 'Create token generation and validation',
            estimatedHours: 4,
          },
          {
            id: '3',
            title: 'Create API endpoints',
            description: 'Build login, register, and refresh endpoints',
            estimatedHours: 3,
          },
        ],
        complexity: 'major',
        estimatedTotalHours: 9,
        suggestedApproach: 'Use industry-standard JWT with refresh tokens',
      };

      mockAITaskService.breakdownTask.mockResolvedValue(mockBreakdown);

      const response = await request(app)
        .post('/api/ai/breakdown/task-456')
        .send({
          taskDescription: 'Implement user authentication with JWT tokens',
          context: 'Node.js Express application with PostgreSQL database',
        })
        .expect(200);

      expect(response.body).toEqual({
        success: true,
        data: mockBreakdown,
        taskId: 'task-456',
      });

      expect(mockAITaskService.breakdownTask).toHaveBeenCalledWith(
        'task-456',
        'Implement user authentication with JWT tokens',
        'Node.js Express application with PostgreSQL database'
      );
    });
  });

  describe('GET /api/ai/health', () => {
    it('should return healthy status when all services are operational', async () => {
      mockAITaskService.getHealthStatus.mockResolvedValue({
        openai: true,
        langchain: true,
        redis: true,
        rateLimiter: true,
      });

      const response = await request(app)
        .get('/api/ai/health')
        .expect(200);

      expect(response.body).toMatchObject({
        success: true,
        healthy: true,
        services: {
          openai: true,
          langchain: true,
          redis: true,
          rateLimiter: true,
        },
        timestamp: expect.any(String),
      });
    });

    it('should return 503 when services are degraded', async () => {
      mockAITaskService.getHealthStatus.mockResolvedValue({
        openai: false,
        langchain: true,
        redis: true,
        rateLimiter: true,
      });

      const response = await request(app)
        .get('/api/ai/health')
        .expect(503);

      expect(response.body).toMatchObject({
        success: true,
        healthy: false,
        services: expect.any(Object),
      });
    });
  });

  describe('GET /api/ai/usage', () => {
    it('should return usage statistics', async () => {
      const mockStats = {
        openai: {
          requestCount: 150,
          totalTokensUsed: 75000,
          estimatedCost: 1.5,
        },
        totalContextsSaved: 300,
        cacheHitRate: 0.68,
      };

      mockAITaskService.getUsageStats.mockResolvedValue(mockStats);

      const response = await request(app)
        .get('/api/ai/usage')
        .expect(200);

      expect(response.body).toEqual({
        success: true,
        data: mockStats,
        timestamp: expect.any(String),
      });
    });
  });

  describe('POST /api/ai/warm-cache', () => {
    it('should initiate cache warming for a project', async () => {
      mockAITaskService.warmCache.mockResolvedValue(undefined);

      const response = await request(app)
        .post('/api/ai/warm-cache')
        .send({
          projectId: 'project-789',
        })
        .expect(202);

      expect(response.body).toEqual({
        success: true,
        message: 'Cache warming initiated',
        projectId: 'project-789',
      });

      expect(mockAITaskService.warmCache).toHaveBeenCalledWith('project-789');
    });

    it('should require projectId', async () => {
      const response = await request(app)
        .post('/api/ai/warm-cache')
        .send({})
        .expect(400);

      expect(response.body).toEqual({
        success: false,
        error: 'Project ID is required',
      });
    });
  });

  describe('POST /api/ai/test-connection', () => {
    it('should test connections to all AI services', async () => {
      mockOpenAIService.testConnection.mockResolvedValue(true);
      mockLangChainService.testConnection.mockResolvedValue(true);

      const response = await request(app)
        .post('/api/ai/test-connection')
        .expect(200);

      expect(response.body).toEqual({
        success: true,
        connections: {
          openai: true,
          langchain: true,
        },
        message: 'All AI services are connected',
      });
    });

    it('should report partial connectivity', async () => {
      mockOpenAIService.testConnection.mockResolvedValue(true);
      mockLangChainService.testConnection.mockResolvedValue(false);

      const response = await request(app)
        .post('/api/ai/test-connection')
        .expect(200);

      expect(response.body).toEqual({
        success: true,
        connections: {
          openai: true,
          langchain: false,
        },
        message: 'Some AI services are not available',
      });
    });
  });

  describe('Error handling', () => {
    it('should handle internal server errors gracefully', async () => {
      mockAITaskService.createTaskFromNaturalLanguage.mockRejectedValue(
        new Error('Database connection failed')
      );

      const response = await request(app)
        .post('/api/ai/create-task')
        .send({
          prompt: 'Create a task',
        })
        .expect(500);

      expect(response.body).toMatchObject({
        success: false,
        error: 'Database connection failed',
      });
    });

    it('should validate request data types', async () => {
      const response = await request(app)
        .post('/api/ai/create-task')
        .send({
          prompt: 123, // Should be string
        })
        .expect(400);

      expect(response.body).toMatchObject({
        success: false,
        error: 'Validation error',
      });
    });
  });

  describe('End-to-end workflow', () => {
    it('should complete full task creation and breakdown flow', async () => {
      // Step 1: Create task from natural language
      const createdTask = {
        id: 'task-new-123',
        title: 'Build user dashboard',
        description: 'Create interactive dashboard for users',
        priority: 'high',
        complexity: 'major',
      };

      mockAITaskService.createTaskFromNaturalLanguage.mockResolvedValue(createdTask);

      const createResponse = await request(app)
        .post('/api/ai/create-task')
        .send({
          prompt: 'Build a user dashboard with charts and metrics',
        })
        .expect(201);

      expect(createResponse.body.success).toBe(true);
      expect(createResponse.body.data).toMatchObject(createdTask);

      // Step 2: Break down the created task
      const breakdown = {
        title: createdTask.title,
        description: createdTask.description,
        subtasks: [
          { id: '1', title: 'Design dashboard layout' },
          { id: '2', title: 'Implement chart components' },
          { id: '3', title: 'Add metrics calculations' },
        ],
        complexity: 'major',
        estimatedTotalHours: 24,
      };

      mockAITaskService.breakdownTask.mockResolvedValue(breakdown);

      const breakdownResponse = await request(app)
        .post(`/api/ai/breakdown/${createdTask.id}`)
        .send({
          taskDescription: createdTask.description,
          context: 'React application with D3.js for charts',
        })
        .expect(200);

      expect(breakdownResponse.body.success).toBe(true);
      expect(breakdownResponse.body.data.subtasks).toHaveLength(3);

      // Step 3: Update context with feedback
      mockAITaskService.updateTaskContext.mockResolvedValue(undefined);

      const updateResponse = await request(app)
        .put(`/api/ai/update-context/${createdTask.id}`)
        .send({
          feedback: 'Focus on real-time data updates for the dashboard',
        })
        .expect(200);

      expect(updateResponse.body.success).toBe(true);
    });
  });
});