import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import express, { Application } from 'express';
import request from 'supertest';
import { setupAgileAPI } from '../../api/agile.js';
import { AgileManager } from '../../../modules/agile-management/agile-manager.js';

// Mock the AgileManager
vi.mock('../../../modules/agile-management/agile-manager.js', () => ({
  AgileManager: vi.fn()
}));

describe('Agile API', () => {
  let app: Application;
  let mockAgileManager: any;

  beforeEach(() => {
    app = express();
    app.use(express.json());

    // Create mock agile manager with all required methods
    mockAgileManager = {
      // Sprint methods
      getAllSprints: vi.fn(),
      getSprintById: vi.fn(),
      getActiveSprint: vi.fn(),
      
      // Story methods
      getAllStories: vi.fn(),
      getStoriesBySprint: vi.fn(),
      getStoriesByStatus: vi.fn(),
      updateStory: vi.fn(),
      getBacklog: vi.fn(),
      
      // Epic methods
      getAllEpics: vi.fn(),
      getEpicById: vi.fn(),
      
      // Velocity and metrics
      calculateVelocity: vi.fn(),
      getBurndownData: vi.fn(),
      getSprintMetrics: vi.fn(),
      
      // Board management
      getBoards: vi.fn(),
      getBoardById: vi.fn(),
      updateBoard: vi.fn(),
      createBoard: vi.fn(),
      
      // Advanced features
      getBacklogItems: vi.fn(),
      prioritizeBacklog: vi.fn(),
      estimateStory: vi.fn(),
      getTeamCapacity: vi.fn(),
      generateSprintReport: vi.fn()
    };

    // Mock constructor
    (AgileManager as any).mockImplementation(() => mockAgileManager);

    // Setup the API
    setupAgileAPI(app, mockAgileManager);
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  describe('GET /api/agile/sprints', () => {
    it('should return all sprints', async () => {
      const mockSprints = [
        {
          id: 'sprint-1',
          name: 'Sprint 1',
          goal: 'Complete MVP',
          status: 'active',
          startDate: new Date('2024-01-01').toISOString(),
          endDate: new Date('2024-01-14').toISOString(),
          storyIds: ['story-1', 'story-2']
        }
      ];

      mockAgileManager.getSprints = vi.fn().mockResolvedValue(mockSprints);

      const response = await request(app).get('/api/agile/sprints');

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveProperty('sprints');
      expect(response.body.data.sprints).toEqual(mockSprints);
      expect(mockAgileManager.getSprints).toHaveBeenCalled();
    });

    it('should handle errors gracefully', async () => {
      mockAgileManager.getSprints = vi.fn().mockRejectedValue(new Error('Database error'));

      const response = await request(app).get('/api/agile/sprints');

      expect(response.status).toBe(500);
      expect(response.body.success).toBe(false);
      expect(response.body.error).toBe('Failed to fetch sprints');
    });

    it('should return mock data when getAllSprints is not implemented', async () => {
      mockAgileManager.getAllSprints = undefined;
      mockAgileManager.getSprints = undefined;

      const response = await request(app).get('/api/agile/sprints');

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveProperty('sprints');
      expect(response.body.data.sprints).toBeInstanceOf(Array);
      expect(response.body.data.sprints[0]).toHaveProperty('id');
      expect(response.body.data.sprints[0]).toHaveProperty('name');
    });
  });

  describe('GET /api/agile/sprints/active', () => {
    it('should return the active sprint', async () => {
      const mockActiveSprint = {
        id: 'sprint-2',
        name: 'Sprint 2',
        status: 'active',
        goal: 'Implement user authentication'
      };

      mockAgileManager.getActiveSprint.mockResolvedValue(mockActiveSprint);

      const response = await request(app).get('/api/agile/sprints/active');

      expect(response.status).toBe(200);
      expect(response.body.data).toEqual(mockActiveSprint);
    });

    it('should return 404 when no active sprint exists', async () => {
      mockAgileManager.getActiveSprint.mockResolvedValue(null);

      const response = await request(app).get('/api/agile/sprints/active');

      expect(response.status).toBe(404);
      expect(response.body.error).toBe('No active sprint found');
    });
  });

  describe('GET /api/agile/sprints/:id', () => {
    it('should return a specific sprint', async () => {
      const mockSprint = {
        id: 'sprint-1',
        name: 'Sprint 1',
        stories: ['story-1', 'story-2']
      };

      mockAgileManager.getSprintById.mockResolvedValue(mockSprint);

      const response = await request(app).get('/api/agile/sprints/sprint-1');

      expect(response.status).toBe(200);
      expect(response.body.data).toEqual(mockSprint);
      expect(mockAgileManager.getSprintById).toHaveBeenCalledWith('sprint-1');
    });
  });

  describe('GET /api/agile/stories', () => {
    it('should return filtered stories', async () => {
      const mockStories = [
        {
          id: 'story-1',
          title: 'User login',
          status: 'in_progress',
          priority: 'high',
          storyPoints: 5
        },
        {
          id: 'story-2',
          title: 'User logout',
          status: 'done',
          priority: 'low',
          storyPoints: 3
        }
      ];

      mockAgileManager.getBacklog = vi.fn().mockResolvedValue(mockStories);

      const response = await request(app)
        .get('/api/agile/stories')
        .query({ status: 'in_progress', priority: 'high' });

      expect(response.status).toBe(200);
      expect(response.body.data).toHaveProperty('stories');
      expect(response.body.data.stories).toHaveLength(1);
      expect(response.body.data.stories[0]).toEqual(mockStories[0]);
      expect(mockAgileManager.getBacklog).toHaveBeenCalled();
    });

    it('should return all stories when no filters provided', async () => {
      const mockStories = [
        { id: 'story-1', title: 'Story 1' },
        { id: 'story-2', title: 'Story 2' }
      ];

      mockAgileManager.getBacklog = vi.fn().mockResolvedValue(mockStories);

      const response = await request(app).get('/api/agile/stories');

      expect(response.status).toBe(200);
      expect(response.body.data).toHaveProperty('stories');
      expect(response.body.data.stories).toEqual(mockStories);
      expect(mockAgileManager.getBacklog).toHaveBeenCalled();
    });
  });

  describe('PUT /api/agile/stories/:id', () => {
    it('should update a story', async () => {
      const updateData = {
        status: 'done',
        hoursSpent: 8
      };

      mockAgileManager.updateStory.mockResolvedValue({
        id: 'story-1',
        ...updateData
      });

      const response = await request(app)
        .put('/api/agile/stories/story-1')
        .send(updateData);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(mockAgileManager.updateStory).toHaveBeenCalledWith('story-1', updateData);
    });
  });

  describe('GET /api/agile/velocity', () => {
    it('should return velocity metrics', async () => {
      const mockVelocity = {
        averageVelocity: 25,
        currentVelocity: 30,
        trend: 'improving',
        sprintData: []
      };

      mockAgileManager.calculateVelocity.mockResolvedValue(mockVelocity);

      const response = await request(app)
        .get('/api/agile/velocity')
        .query({ teamName: 'Alpha Team', sprints: '5' });

      expect(response.status).toBe(200);
      expect(response.body.data).toEqual(mockVelocity);
      expect(mockAgileManager.calculateVelocity).toHaveBeenCalledWith({
        teamName: 'Alpha Team',
        lastNSprints: 5
      });
    });
  });

  describe('GET /api/agile/burndown/:sprintId', () => {
    it('should return burndown chart data', async () => {
      const mockBurndown = {
        sprintId: 'sprint-1',
        totalStoryPoints: 30,
        completedPoints: 15,
        dailyData: []
      };

      mockAgileManager.getBurndownData.mockResolvedValue(mockBurndown);

      const response = await request(app).get('/api/agile/burndown/sprint-1');

      expect(response.status).toBe(200);
      expect(response.body.data).toEqual(mockBurndown);
    });
  });

  describe('GET /api/agile/boards', () => {
    it('should return all boards', async () => {
      const mockBoards = [
        {
          id: 'board-1',
          name: 'Development Board',
          columns: ['To Do', 'In Progress', 'Done']
        }
      ];

      mockAgileManager.getBoards.mockResolvedValue(mockBoards);

      const response = await request(app).get('/api/agile/boards');

      expect(response.status).toBe(200);
      expect(response.body.data).toEqual(mockBoards);
    });
  });

  describe('POST /api/agile/boards', () => {
    it('should create a new board', async () => {
      const newBoard = {
        name: 'QA Board',
        columns: ['Ready for QA', 'Testing', 'Verified']
      };

      mockAgileManager.createBoard.mockResolvedValue({
        id: 'board-2',
        ...newBoard
      });

      const response = await request(app)
        .post('/api/agile/boards')
        .send(newBoard);

      expect(response.status).toBe(201);
      expect(response.body.success).toBe(true);
      expect(mockAgileManager.createBoard).toHaveBeenCalledWith(newBoard);
    });

    it('should validate required fields', async () => {
      const response = await request(app)
        .post('/api/agile/boards')
        .send({ columns: ['Test'] }); // missing name

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Board name and columns are required');
    });
  });

  describe('GET /api/agile/backlog', () => {
    it('should return prioritized backlog items', async () => {
      const mockBacklog = [
        {
          id: 'story-10',
          title: 'High priority feature',
          priority: 'high',
          storyPoints: 8
        }
      ];

      mockAgileManager.getBacklogItems.mockResolvedValue(mockBacklog);

      const response = await request(app)
        .get('/api/agile/backlog')
        .query({ includeEstimates: 'true', maxItems: '20' });

      expect(response.status).toBe(200);
      expect(response.body.data).toEqual(mockBacklog);
      expect(mockAgileManager.getBacklogItems).toHaveBeenCalledWith({
        includeEstimates: true,
        maxItems: 20
      });
    });
  });

  describe('POST /api/agile/estimate', () => {
    it('should estimate a story', async () => {
      const estimateData = {
        storyId: 'story-15',
        estimates: { dev1: 5, dev2: 8, dev3: 5 },
        finalEstimate: 5
      };

      mockAgileManager.estimateStory.mockResolvedValue({
        storyId: 'story-15',
        estimate: 5,
        confidence: 85
      });

      const response = await request(app)
        .post('/api/agile/estimate')
        .send(estimateData);

      expect(response.status).toBe(200);
      expect(mockAgileManager.estimateStory).toHaveBeenCalledWith(
        'story-15',
        estimateData
      );
    });
  });

  describe('GET /api/agile/capacity/:sprintId', () => {
    it('should return team capacity for sprint', async () => {
      const mockCapacity = {
        totalHours: 320,
        totalStoryPoints: 40,
        teamMembers: []
      };

      mockAgileManager.getTeamCapacity.mockResolvedValue(mockCapacity);

      const response = await request(app).get('/api/agile/capacity/sprint-1');

      expect(response.status).toBe(200);
      expect(response.body.data).toEqual(mockCapacity);
    });
  });

  describe('GET /api/agile/report/:sprintId', () => {
    it('should generate sprint report', async () => {
      const mockReport = {
        sprint: { id: 'sprint-1', name: 'Sprint 1' },
        metrics: { velocity: 25, completionRate: 85 },
        highlights: ['Completed authentication'],
        issues: ['Technical debt in payment module']
      };

      mockAgileManager.generateSprintReport.mockResolvedValue(mockReport);

      const response = await request(app)
        .get('/api/agile/report/sprint-1')
        .query({ includeMetrics: 'true', format: 'detailed' });

      expect(response.status).toBe(200);
      expect(response.body.data).toEqual(mockReport);
      expect(mockAgileManager.generateSprintReport).toHaveBeenCalledWith('sprint-1', {
        includeMetrics: true,
        includeStories: true,
        includeRetrospective: false,
        format: 'detailed'
      });
    });
  });
});