import 'reflect-metadata';
import { Request, Response, NextFunction } from 'express';
import { z } from 'zod';

// Mock dependencies before importing controller
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;
    }),
  },
  injectable: jest.fn(() => (target: any) => target),
  inject: jest.fn(() => (target: any, key: any, index: any) => {}),
}));

// 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(),
};

import { AIController } from '../../../src/api/controllers/ai.controller';

describe('AIController', () => {
  let controller: AIController;
  let mockReq: Partial<Request>;
  let mockRes: Partial<Response>;
  let mockNext: NextFunction;

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

    controller = new AIController();

    mockReq = {
      body: {},
      params: {},
      query: {},
    };

    mockRes = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn().mockReturnThis(),
    };

    mockNext = jest.fn();
  });

  describe('createTask', () => {
    it('should create a task from natural language', async () => {
      const mockTask = {
        title: 'Implement login',
        description: 'Add user authentication',
        priority: 'high',
        complexity: 'major',
      };

      mockAITaskService.createTaskFromNaturalLanguage.mockResolvedValue(mockTask);

      mockReq.body = {
        prompt: 'Create a login feature',
        projectId: 'project-123',
      };

      await controller.createTask(mockReq as Request, mockRes as Response, mockNext);

      expect(mockAITaskService.createTaskFromNaturalLanguage).toHaveBeenCalledWith({
        prompt: 'Create a login feature',
        projectId: 'project-123',
        context: undefined,
      });

      expect(mockRes.status).toHaveBeenCalledWith(201);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data: mockTask,
        message: 'Task created successfully from natural language',
      });
    });

    it('should handle validation errors', async () => {
      mockReq.body = {
        // Missing required 'prompt' field
        projectId: 'project-123',
      };

      await controller.createTask(mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(400);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: 'Validation error',
        details: expect.any(Array),
      });
    });

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

      mockReq.body = {
        prompt: 'Create a task',
      };

      await controller.createTask(mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(429);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: 'Rate limit exceeded',
        message: 'Too many requests. Please try again later.',
      });
    });
  });

  describe('updateTaskContext', () => {
    it('should update task context with feedback', async () => {
      mockAITaskService.updateTaskContext.mockResolvedValue(undefined);

      mockReq.params = { taskId: 'task-123' };
      mockReq.body = { feedback: 'Task needs more detail' };

      await controller.updateTaskContext(mockReq as Request, mockRes as Response, mockNext);

      expect(mockAITaskService.updateTaskContext).toHaveBeenCalledWith(
        'task-123',
        'Task needs more detail'
      );

      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        message: 'Task context updated successfully',
      });
    });

    it('should validate feedback is provided', async () => {
      mockReq.params = { taskId: 'task-123' };
      mockReq.body = {}; // Missing feedback

      await controller.updateTaskContext(mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(400);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: 'Validation error',
        details: expect.any(Array),
      });
    });
  });

  describe('getTaskSuggestions', () => {
    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',
          priority: 'medium',
          confidence: 0.85,
        },
      ];

      mockAITaskService.getTaskSuggestions.mockResolvedValue(mockSuggestions);

      mockReq.params = { projectId: 'project-123' };
      mockReq.query = {
        projectDescription: 'E-commerce platform',
        completedTasks: 'Setup database,Create API',
        currentTasks: 'Build UI',
        goals: 'Launch MVP',
      };

      await controller.getTaskSuggestions(mockReq as Request, mockRes as Response, mockNext);

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

      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data: mockSuggestions,
        projectId: 'project-123',
      });
    });

    it('should handle empty arrays for task lists', async () => {
      mockAITaskService.getTaskSuggestions.mockResolvedValue([]);

      mockReq.params = { projectId: 'project-456' };
      mockReq.query = {
        projectDescription: 'New project',
        // No completed/current tasks or goals
      };

      await controller.getTaskSuggestions(mockReq as Request, mockRes as Response, mockNext);

      expect(mockAITaskService.getTaskSuggestions).toHaveBeenCalledWith(
        'project-456',
        {
          projectDescription: 'New project',
          completedTasks: [],
          currentTasks: [],
          goals: [],
        }
      );
    });
  });

  describe('breakdownTask', () => {
    it('should break down a task into subtasks', async () => {
      const mockBreakdown = {
        title: 'Main task',
        description: 'Task description',
        subtasks: [
          { id: '1', title: 'Subtask 1', description: 'First part' },
          { id: '2', title: 'Subtask 2', description: 'Second part' },
        ],
        complexity: 'major',
        estimatedTotalHours: 8,
      };

      mockAITaskService.breakdownTask.mockResolvedValue(mockBreakdown);

      mockReq.params = { taskId: 'task-789' };
      mockReq.body = {
        taskDescription: 'Implement authentication system',
        context: 'Web application using JWT',
      };

      await controller.breakdownTask(mockReq as Request, mockRes as Response, mockNext);

      expect(mockAITaskService.breakdownTask).toHaveBeenCalledWith(
        'task-789',
        'Implement authentication system',
        'Web application using JWT'
      );

      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data: mockBreakdown,
        taskId: 'task-789',
      });
    });
  });

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

      await controller.getHealthStatus(mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        healthy: true,
        services: {
          openai: true,
          langchain: true,
          redis: true,
          rateLimiter: true,
        },
        timestamp: expect.any(String),
      });
    });

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

      await controller.getHealthStatus(mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(503);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        healthy: false,
        services: expect.any(Object),
        timestamp: expect.any(String),
      });
    });
  });

  describe('getUsageStats', () => {
    it('should return usage statistics', async () => {
      const mockStats = {
        openai: {
          requestCount: 100,
          totalTokensUsed: 50000,
          estimatedCost: 1.0,
        },
        totalContextsSaved: 250,
        cacheHitRate: 0.75,
      };

      mockAITaskService.getUsageStats.mockResolvedValue(mockStats);

      await controller.getUsageStats(mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data: mockStats,
        timestamp: expect.any(String),
      });
    });
  });

  describe('warmCache', () => {
    it('should initiate cache warming', async () => {
      mockAITaskService.warmCache.mockResolvedValue(undefined);

      mockReq.body = { projectId: 'project-123' };

      await controller.warmCache(mockReq as Request, mockRes as Response, mockNext);

      expect(mockAITaskService.warmCache).toHaveBeenCalledWith('project-123');

      expect(mockRes.status).toHaveBeenCalledWith(202);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        message: 'Cache warming initiated',
        projectId: 'project-123',
      });
    });

    it('should require projectId', async () => {
      mockReq.body = {}; // Missing projectId

      await controller.warmCache(mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(400);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: 'Project ID is required',
      });
    });
  });

  describe('testConnection', () => {
    it('should test connections to AI services', async () => {
      mockOpenAIService.testConnection.mockResolvedValue(true);
      mockLangChainService.testConnection.mockResolvedValue(true);

      await controller.testConnection(mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        connections: {
          openai: true,
          langchain: true,
        },
        message: 'All AI services are connected',
      });
    });

    it('should report when some services are unavailable', async () => {
      mockOpenAIService.testConnection.mockResolvedValue(true);
      mockLangChainService.testConnection.mockResolvedValue(false);

      await controller.testConnection(mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        connections: {
          openai: true,
          langchain: false,
        },
        message: 'Some AI services are not available',
      });
    });
  });

  describe('error handling', () => {
    it('should call next with non-validation errors', async () => {
      const error = new Error('Database error');
      mockAITaskService.createTaskFromNaturalLanguage.mockRejectedValue(error);

      mockReq.body = { prompt: 'Create task' };

      await controller.createTask(mockReq as Request, mockRes as Response, mockNext);

      expect(mockNext).toHaveBeenCalledWith(error);
    });

    it('should handle Zod validation errors with details', async () => {
      mockReq.body = {
        prompt: '', // Empty string should fail validation
      };

      await controller.createTask(mockReq as Request, mockRes as Response, mockNext);

      expect(mockRes.status).toHaveBeenCalledWith(400);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: 'Validation error',
        details: expect.arrayContaining([
          expect.objectContaining({
            path: expect.any(Array),
            message: expect.any(String),
          }),
        ]),
      });
    });
  });
});