import { describe, it, expect, beforeEach, vi } from 'vitest';
import { setupAgileManagementTools } from '../tools.js';
import { SqliteManager } from '../../../storage/sqlite-manager.js';

// Mock SqliteManager
vi.mock('../../../storage/sqlite-manager.js', () => ({
  SqliteManager: {
    getInstance: vi.fn()
  }
}));

describe('Agile Management Tools', () => {
  let tools: any;
  let mockDb: vi.Mocked<SqliteManager>;

  beforeEach(async () => {
    vi.clearAllMocks();
    
    // Mock database operations
    mockDb = {
      run: vi.fn().mockResolvedValue({ success: true, changes: 1 }),
      get: vi.fn().mockResolvedValue({ success: true, data: null }),
      query: vi.fn().mockResolvedValue({ success: true, data: [] }),
      transaction: vi.fn().mockImplementation(async (callback) => {
        return await callback(mockDb);
      })
    } as any;
    
    vi.mocked(SqliteManager.getInstance).mockReturnValue(mockDb);
    
    // Setup tools
    const toolRegistration = await setupAgileManagementTools();
    tools = {};
    for (const tool of toolRegistration.tools) {
      tools[tool.name] = tool;
    }
  });

  const createContext = (toolName: string) => ({
    toolName,
    requestId: 'test-req-1',
    projectId: 'test-project',
    userId: 'test-user',
    timestamp: Date.now(),
    db: mockDb
  });

  describe('create_agile_sprint', () => {
    it('should create a new sprint with default values', async () => {
      const input = {
        name: 'Sprint 1',
        goal: 'Implement user authentication',
        duration: 14,
      };

      const result = await tools.create_agile_sprint.execute(input, createContext('create_agile_sprint'));

      expect(result.success).toBe(true);
      expect(result.data).toBeDefined();
      expect(result.data.sprint).toBeDefined();
      expect(result.data.sprint.name).toBe('Sprint 1');
      expect(result.data.sprint.goal).toBe('Implement user authentication');
      expect(result.data.sprint.duration).toBe(14);
      expect(result.data.sprint.status).toBe('active');
      expect(result.data.sprint.id).toMatch(/^[a-zA-Z0-9-]+$/);
      expect(mockDb.run).toHaveBeenCalled();
    });

    it('should create sprint with team members', async () => {
      const input = {
        name: 'Sprint 2',
        goal: 'Build dashboard',
        duration: 10,
        team: ['alice@example.com', 'bob@example.com'],
      };

      const result = await tools.create_agile_sprint.execute(input, createContext('create_agile_sprint'));

      expect(result.success).toBe(true);
      expect(result.data).toBeDefined();
      expect(result.data.sprint).toBeDefined();
      expect(result.data.sprint.team).toEqual(['alice@example.com', 'bob@example.com']);
    });
  });

  describe('create_user_story', () => {
    it('should create a new story', async () => {
      const input = {
        title: 'User login functionality',
        description: 'As a user, I want to log in',
        priority: 'high' as const,
        storyPoints: 5
      };

      const result = await tools.create_user_story.execute(input, createContext('create_user_story'));

      expect(result.success).toBe(true);
      expect(result.data).toBeDefined();
      expect(result.data.story).toBeDefined();
      expect(result.data.story.title).toBe('User login functionality');
      expect(result.data.story.description).toBe('As a user, I want to log in');
      expect(result.data.story.priority).toBe('high');
      expect(result.data.story.storyPoints).toBe(5);
      expect(result.data.story.id).toMatch(/^[a-zA-Z0-9-]+$/);
      expect(mockDb.run).toHaveBeenCalled();
    });
  });

  describe('update_story_status', () => {
    it('should update story status', async () => {
      // Mock story check
      mockDb.get.mockResolvedValueOnce({
        success: true,
        data: {
          id: 'STORY-0001',
          title: 'Test Story',
          status: 'todo'
        }
      });

      const result = await tools.update_story_status.execute({
        storyId: 'STORY-0001',
        status: 'in_progress',
        notes: 'Started working on this',
        skipWorkflowValidation: true // Skip validation for this test
      }, createContext('update_story_status'));

      expect(result.success).toBe(true);
      expect(result.data).toBeDefined();
      expect(result.data.oldStatus).toBe('todo');
      expect(result.data.newStatus).toBe('in_progress');
      expect(mockDb.run).toHaveBeenCalled();
    });
  });

  describe('get_sprint_stats', () => {
    it('should get sprint statistics', async () => {
      // Mock sprint data
      mockDb.get.mockResolvedValueOnce({
        success: true,
        data: {
          id: 'SPRINT-0001',
          name: 'Sprint 1',
          goal: 'Test goal',
          status: 'active',
          duration_days: 14,
          start_date: Date.now(),
          end_date: Date.now() + (14 * 24 * 60 * 60 * 1000),
          team_members: '[]'
        }
      });
      
      // Mock stats data
      mockDb.query.mockResolvedValueOnce({
        success: true,
        data: [{
          total_stories: 5,
          completed_stories: 2,
          in_progress_stories: 1,
          review_stories: 1,
          todo_stories: 1,
          total_points: 20,
          completed_points: 8
        }]
      });

      const result = await tools.get_sprint_stats.execute({ sprintId: 'SPRINT-0001' }, createContext('get_sprint_stats'));

      expect(result.success).toBe(true);
      expect(result.data).toBeDefined();
      expect(result.data.sprint).toBeDefined();
      expect(result.data.sprint.name).toBe('Sprint 1');
      expect(result.data.progress).toBeDefined();
      expect(result.data.progress.stories).toBeDefined();
      expect(result.data.progress.stories.total).toBe(5);
      expect(mockDb.get).toHaveBeenCalled();
      expect(mockDb.query).toHaveBeenCalled();
    });
  });

  describe('list_stories', () => {
    it('should list stories with no filters', async () => {
      mockDb.query.mockResolvedValueOnce({
        success: true,
        data: [
          {
            id: 'STORY-0001',
            title: 'Test Story',
            description: 'Test description',
            status: 'todo',
            priority: 'medium',
            story_points: 3,
            acceptance_criteria: '[]',
            tags: '[]',
            assigned_to: null,
            sprint_id: null,
            epic_id: null,
            created_at: Date.now(),
            updated_at: Date.now()
          }
        ]
      });

      const result = await tools.list_stories.execute({}, createContext('list_stories'));

      expect(result.success).toBe(true);
      expect(result.data).toBeDefined();
      expect(result.data.stories).toBeDefined();
      expect(result.data.stories).toHaveLength(1);
      expect(result.data.stories[0].title).toBe('Test Story');
      expect(mockDb.query).toHaveBeenCalled();
    });
  });

  describe('add_story_to_sprint', () => {
    it('should add story to sprint', async () => {
      // Mock story check
      mockDb.get.mockResolvedValueOnce({
        success: true,
        data: {
          id: 'STORY-0001',
          title: 'Test Story',
          sprint_id: null
        }
      });
      
      // Mock sprint check
      mockDb.get.mockResolvedValueOnce({
        success: true,
        data: {
          id: 'SPRINT-0001',
          name: 'Sprint 1',
          status: 'planning'
        }
      });

      const result = await tools.add_story_to_sprint.execute({
        storyId: 'STORY-0001',
        sprintId: 'SPRINT-0001'
      }, createContext('add_story_to_sprint'));

      expect(result.success).toBe(true);
      expect(result.data).toBeDefined();
      expect(result.data.message).toContain('Test Story');
      expect(result.data.message).toContain('Sprint 1');
      expect(mockDb.run).toHaveBeenCalled();
    });
  });

  describe('list_stories_with_details', () => {
    it('should list stories with documentation fields', async () => {
      mockDb.query.mockResolvedValueOnce({
        success: true,
        data: [
          {
            id: 'STORY-0001',
            title: 'Test Story with Docs',
            description: 'Test description',
            status: 'in_progress',
            priority: 'high',
            story_points: 5,
            implementation_doc_url: 'https://docs.example.com/impl',
            design_doc_url: 'https://docs.example.com/design',
            documentation_status: 'approved',
            acceptance_criteria: '[]',
            tags: '[]',
            assigned_to: 'john@example.com',
            sprint_id: 'SPRINT-0001',
            epic_id: null,
            created_at: Date.now(),
            updated_at: Date.now()
          },
          {
            id: 'STORY-0002',
            title: 'Test Story without Docs',
            description: 'Another test',
            status: 'todo',
            priority: 'medium',
            story_points: 3,
            implementation_doc_url: null,
            design_doc_url: null,
            documentation_status: null,
            acceptance_criteria: '[]',
            tags: '[]',
            assigned_to: null,
            sprint_id: 'SPRINT-0001',
            epic_id: null,
            created_at: Date.now(),
            updated_at: Date.now()
          }
        ]
      });

      const result = await tools.list_stories_with_details.execute({
        sprintId: 'SPRINT-0001',
        includeFields: ['id', 'title', 'status', 'implementationDocumentUrl', 'designDocumentUrl', 'documentationStatus']
      }, createContext('list_stories_with_details'));

      expect(result.success).toBe(true);
      expect(result.data).toBeDefined();
      expect(result.data.stories).toBeDefined();
      expect(result.data.stories).toHaveLength(2);
      expect(result.data.stories[0].implementationDocumentUrl).toBe('https://docs.example.com/impl');
      expect(result.data.stories[1].implementationDocumentUrl).toBeNull();
      expect(result.data.summary).toBeDefined();
      expect(result.data.summary.missingDocumentation).toBe(1);
      expect(mockDb.query).toHaveBeenCalled();
    });

    it('should filter by status', async () => {
      mockDb.query.mockResolvedValueOnce({
        success: true,
        data: [
          {
            id: 'STORY-0001',
            title: 'In Progress Story',
            status: 'in_progress',
            story_points: 5,
            sprint_id: 'SPRINT-0001'
          }
        ]
      });

      const result = await tools.list_stories_with_details.execute({
        status: 'in_progress',
        includeFields: ['id', 'title', 'status']
      }, createContext('list_stories_with_details'));

      expect(result.success).toBe(true);
      expect(result.data).toBeDefined();
      expect(result.data.stories).toBeDefined();
      expect(result.data.stories).toHaveLength(1);
      expect(result.data.stories[0].status).toBe('in_progress');
    });
  });

  describe('tool registration', () => {
    it('should register all expected tools', () => {
      const expectedTools = [
        'create_agile_sprint',
        'create_user_story',
        'add_story_to_sprint',
        'update_story_status',
        'get_sprint_stats',
        'list_stories',
        'list_stories_with_details'
      ];

      for (const toolName of expectedTools) {
        expect(tools[toolName]).toBeDefined();
        expect(typeof tools[toolName].execute).toBe('function');
      }
    });
  });

  describe('error handling', () => {
    it('should handle database errors gracefully', async () => {
      mockDb.run.mockResolvedValueOnce({ success: false, error: 'Database error' });

      const input = {
        name: 'Sprint 1',
        goal: 'Test goal',
        duration: 14,
      };

      const result = await tools.create_agile_sprint.execute(input, createContext('create_agile_sprint'));

      expect(result.success).toBe(false);
      expect(result.error).toBeDefined();
    });
  });
});