import { describe, it, expect, beforeEach, vi } from 'vitest';
import request from 'supertest';
import express from 'express';
import { setupApprovalsAPI } from '../../api/approvals.js';
import { getSQLiteManager, ensureDatabaseReady } from '../../../storage/sqlite-manager.js';

// Mock the SQLite manager
vi.mock('../../../storage/sqlite-manager.js', () => ({
  getSQLiteManager: vi.fn(),
  ensureDatabaseReady: vi.fn()
}));

const mockSQLiteManager = {
  query: vi.fn(),
  get: vi.fn(),
  run: vi.fn(),
  initialize: vi.fn(),
  close: vi.fn()
};

describe('Approvals API', () => {
  let app: express.Application;

  beforeEach(() => {
    app = express();
    app.use(express.json());
    
    // Reset mocks
    vi.clearAllMocks();
    vi.mocked(getSQLiteManager).mockReturnValue(mockSQLiteManager as any);
    vi.mocked(ensureDatabaseReady).mockResolvedValue(mockSQLiteManager as any);
    
    // Setup the approvals API
    setupApprovalsAPI(app);
  });

  describe('GET /api/approvals', () => {
    it('should return all approval requests', async () => {
      const mockApprovals = [
        {
          id: 'approval-123',
          action: 'create_sprint',
          context: '{"sprintName": "Sprint 1"}',
          description: 'Create new sprint',
          urgency: 'medium',
          status: 'pending',
          requested_by: 'user-1',
          requested_at: Date.now(),
          resolved_by: null,
          resolved_at: null,
          timeout_at: Date.now() + 3600000,
          response_data: null,
          notes: null
        }
      ];

      mockSQLiteManager.query.mockResolvedValue({
        success: true,
        data: mockApprovals
      });

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

      expect(response.body.success).toBe(true);
      expect(response.body.data.approvals).toHaveLength(1);
      expect(response.body.data.approvals[0].id).toBe('approval-123');
      expect(response.body.data.approvals[0].action).toBe('create_sprint');
      expect(response.body.data.approvals[0].context).toEqual({ sprintName: 'Sprint 1' });
    });

    it('should filter by status', async () => {
      mockSQLiteManager.query.mockResolvedValue({
        success: true,
        data: []
      });

      await request(app)
        .get('/api/approvals?status=pending')
        .expect(200);

      expect(mockSQLiteManager.query).toHaveBeenCalledWith(
        expect.stringContaining('AND status = ?'),
        expect.arrayContaining(['pending'])
      );
    });

    it('should handle database errors', async () => {
      mockSQLiteManager.query.mockResolvedValue({
        success: false,
        error: 'Database connection failed'
      });

      const response = await request(app)
        .get('/api/approvals')
        .expect(500);

      expect(response.body.success).toBe(false);
      expect(response.body.error).toBe('Failed to fetch approval requests');
    });
  });

  describe('GET /api/approvals/:id', () => {
    it('should return specific approval request', async () => {
      const mockApproval = {
        id: 'approval-123',
        action: 'create_sprint',
        context: '{"sprintName": "Sprint 1"}',
        description: 'Create new sprint',
        urgency: 'medium',
        status: 'pending',
        requested_by: 'user-1',
        requested_at: Date.now(),
        resolved_by: null,
        resolved_at: null,
        timeout_at: Date.now() + 3600000,
        response_data: null,
        notes: null
      };

      mockSQLiteManager.get.mockResolvedValue({
        success: true,
        data: mockApproval
      });

      const response = await request(app)
        .get('/api/approvals/approval-123')
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data.id).toBe('approval-123');
      expect(response.body.data.context).toEqual({ sprintName: 'Sprint 1' });
    });

    it('should return 404 for non-existent approval', async () => {
      mockSQLiteManager.get.mockResolvedValue({
        success: true,
        data: null
      });

      const response = await request(app)
        .get('/api/approvals/non-existent')
        .expect(404);

      expect(response.body.success).toBe(false);
      expect(response.body.error).toBe('Approval request not found');
    });
  });

  describe('POST /api/approvals/:id/respond', () => {
    it('should approve an approval request', async () => {
      mockSQLiteManager.run.mockResolvedValue({
        success: true,
        data: { changes: 1 }
      });

      const response = await request(app)
        .post('/api/approvals/approval-123/respond')
        .send({
          decision: 'approved',
          notes: 'Looks good',
          resolvedBy: 'admin-user'
        })
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data.decision).toBe('approved');
      expect(response.body.data.resolvedBy).toBe('admin-user');
    });

    it('should reject invalid decisions', async () => {
      const response = await request(app)
        .post('/api/approvals/approval-123/respond')
        .send({
          decision: 'invalid'
        })
        .expect(400);

      expect(response.body.success).toBe(false);
      expect(response.body.error).toBe('Invalid decision. Must be "approved" or "rejected"');
    });

    it('should handle already resolved requests', async () => {
      mockSQLiteManager.run.mockResolvedValue({
        success: true,
        data: { changes: 0 }
      });

      const response = await request(app)
        .post('/api/approvals/approval-123/respond')
        .send({
          decision: 'approved'
        })
        .expect(404);

      expect(response.body.success).toBe(false);
      expect(response.body.error).toBe('Approval request not found or already resolved');
    });
  });

  describe('GET /api/approvals/stats', () => {
    it('should return approval statistics', async () => {
      // Mock individual count queries
      mockSQLiteManager.get
        .mockResolvedValueOnce({ success: true, data: { total: 10 } })
        .mockResolvedValueOnce({ success: true, data: { pending: 3 } })
        .mockResolvedValueOnce({ success: true, data: { approved: 5 } })
        .mockResolvedValueOnce({ success: true, data: { rejected: 2 } })
        .mockResolvedValueOnce({ success: true, data: { expired: 0 } });

      // Mock urgency breakdown query
      mockSQLiteManager.query.mockResolvedValue({
        success: true,
        data: [
          { urgency: 'high', count: 4 },
          { urgency: 'medium', count: 5 },
          { urgency: 'low', count: 1 }
        ]
      });

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

      expect(response.body.success).toBe(true);
      expect(response.body.data.total).toBe(10);
      expect(response.body.data.pending).toBe(3);
      expect(response.body.data.approved).toBe(5);
      expect(response.body.data.urgencyBreakdown).toEqual({
        high: 4,
        medium: 5,
        low: 1
      });
    });

    it('should handle different timeframes', async () => {
      // Setup mock responses for all stats queries in sequence
      mockSQLiteManager.get
        .mockResolvedValueOnce({ success: true, data: { total: 5 } })
        .mockResolvedValueOnce({ success: true, data: { pending: 2 } })
        .mockResolvedValueOnce({ success: true, data: { approved: 2 } })
        .mockResolvedValueOnce({ success: true, data: { rejected: 1 } })
        .mockResolvedValueOnce({ success: true, data: { expired: 0 } });
      
      mockSQLiteManager.query.mockResolvedValue({ success: true, data: [] });

      await request(app)
        .get('/api/approvals/stats?timeframe=24h')
        .expect(200);

      expect(mockSQLiteManager.get).toHaveBeenCalledWith(
        expect.stringContaining('WHERE requested_at > ?'),
        expect.arrayContaining([expect.any(Number)])
      );
    });
  });

  describe('GET /api/approvals/health', () => {
    it('should return healthy status', async () => {
      // Provide a simple mock that won't trigger the invalid timestamp conversion
      mockSQLiteManager.get.mockResolvedValue({
        success: true,
        data: { count: 0 }
      });

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

      expect(response.body.success).toBe(true);
      expect(response.body.status).toBe('healthy');
      expect(response.body.data.databaseConnected).toBe(true);
    });

    it('should return unhealthy status on database failure', async () => {
      mockSQLiteManager.get.mockResolvedValue({
        success: false,
        error: 'Database connection failed'
      });

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

      expect(response.body.success).toBe(false);
      expect(response.body.status).toBe('unhealthy');
    });
  });
});