/**
 * Tests for get_voting_records MCP tool
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { handleGetVotingRecords, getVotingRecordsToolMetadata } from './getVotingRecords.js';
import * as epClientModule from '../clients/europeanParliamentClient.js';
import { setupToolTest } from '../../tests/helpers/mockFactory.js';
import { expectValidMCPResponse, expectValidPaginatedMCPResponse } from '../../tests/helpers/assertions.js';

// Mock the EP client
vi.mock('../clients/europeanParliamentClient.js', () => ({
  epClient: {
    getVotingRecords: vi.fn()
  }
}));

// Registers beforeEach(vi.clearAllMocks) for all tests in this file
setupToolTest();

describe('get_voting_records Tool', () => {
  beforeEach(() => {
    // Setup default mock implementation
    vi.mocked(epClientModule.epClient.getVotingRecords).mockResolvedValue({
      data: [
        {
          id: 'VOTE-2024-001',
          sessionId: 'MTG-PL-2024-01-15',
          topic: 'Resolution on climate change',
          date: '2024-01-15',
          votesFor: 450,
          votesAgainst: 150,
          abstentions: 50,
          result: 'ADOPTED'
        }
      ],
      total: 1,
      limit: 50,
      offset: 0,
      hasMore: false
    });
  });

  describe('Input Validation', () => {
    it('should accept request with no filters', async () => {
      const result = await handleGetVotingRecords({});
      expect(result).toHaveProperty('content');
      expect(result.content[0]?.type).toBe('text');
    });

    it('should accept valid session ID', async () => {
      const result = await handleGetVotingRecords({ sessionId: 'PLENARY-2024-01' });
      expect(result).toHaveProperty('content');
    });

    it('should accept valid topic', async () => {
      const result = await handleGetVotingRecords({ topic: 'Climate Change' });
      expect(result).toHaveProperty('content');
    });

    it('should accept valid date range', async () => {
      const result = await handleGetVotingRecords({ 
        dateFrom: '2024-01-01',
        dateTo: '2024-12-31'
      });
      expect(result).toHaveProperty('content');
    });

    it('should reject invalid date format', async () => {
      await expect(handleGetVotingRecords({ dateFrom: '01/01/2024' }))
        .rejects.toThrow();
    });

    it('should accept valid limit', async () => {
      const result = await handleGetVotingRecords({ limit: 50 });
      expect(result).toHaveProperty('content');
    });

    it('should reject limit below minimum', async () => {
      await expect(handleGetVotingRecords({ limit: 0 }))
        .rejects.toThrow();
    });

    it('should reject limit above maximum', async () => {
      await expect(handleGetVotingRecords({ limit: 101 }))
        .rejects.toThrow();
    });

    it('should reject removed/unknown parameters (e.g. deprecated mepId)', async () => {
      // Strict schema → unrecognized_keys issue with empty path → formatted as (root)
      await expect(handleGetVotingRecords({ mepId: 'MEP-12345' }))
        .rejects.toThrow(/Invalid parameters:\s+\(root\):.*mepId/);
    });
  });

  describe('Response Format', () => {
    it('should return MCP-compliant response structure', async () => {
      const result = await handleGetVotingRecords({});
      expectValidMCPResponse(result);
    });

    it('should include mock vote data with id, topic and result in payload', async () => {
      const result = await handleGetVotingRecords({});
      const parsed = expectValidPaginatedMCPResponse(result);
      const first = parsed.data[0] as Record<string, unknown> | undefined;
      expect(first).toBeDefined();
      expect(first?.['id']).toBe('VOTE-2024-001');
      expect(first?.['topic']).toBe('Resolution on climate change');
      expect(first?.['result']).toBe('ADOPTED');
    });

    it('should return paginated response structure', async () => {
      const result = await handleGetVotingRecords({});
      expectValidPaginatedMCPResponse(result);
    });
  });

  describe('Error Handling', () => {
    it('should provide clean error messages', async () => {
      try {
        await handleGetVotingRecords({ limit: -5 });
        expect.fail('Should have thrown error');
      } catch (error) {
        expect(error).toBeInstanceOf(Error);
        const err = error as Error;
        expect(err.message).toBeTruthy();
      }
    });

    it('should not expose internal errors', async () => {
      const spy = vi.spyOn(epClientModule.epClient, 'getVotingRecords')
        .mockRejectedValueOnce(new Error('Database error'));

      try {
        await handleGetVotingRecords({});
        expect.fail('Should have thrown error');
      } catch (error) {
        expect(error).toBeInstanceOf(Error);
        const err = error as Error;
        expect(err.message).toContain('Failed to retrieve voting records');
      }

      spy.mockRestore();
    });
  });

  describe('Edge Cases', () => {
    it('should handle empty API response gracefully', async () => {
      vi.mocked(epClientModule.epClient.getVotingRecords).mockResolvedValue({
        data: [],
        total: 0,
        limit: 50,
        offset: 0,
        hasMore: false,
      });

      const result = await handleGetVotingRecords({});
      const data = JSON.parse(result.content[0]?.text ?? '{}') as {
        data: unknown[];
        total: number;
        hasMore: boolean;
      };
      expect(data.data).toEqual([]);
      expect(data.total).toBe(0);
      expect(data.hasMore).toBe(false);
    });

    it('should handle minimum limit of 1', async () => {
      vi.mocked(epClientModule.epClient.getVotingRecords).mockResolvedValue({
        data: [],
        total: 0,
        limit: 1,
        offset: 0,
        hasMore: false,
      });
      await handleGetVotingRecords({ limit: 1 });
      expect(epClientModule.epClient.getVotingRecords).toHaveBeenCalledWith(
        expect.objectContaining({ limit: 1 })
      );
    });

    it('should handle maximum limit of 100', async () => {
      vi.mocked(epClientModule.epClient.getVotingRecords).mockResolvedValue({
        data: [],
        total: 0,
        limit: 100,
        offset: 0,
        hasMore: false,
      });
      await handleGetVotingRecords({ limit: 100 });
      expect(epClientModule.epClient.getVotingRecords).toHaveBeenCalledWith(
        expect.objectContaining({ limit: 100 })
      );
    });

    it('should handle large offset (pagination boundary)', async () => {
      vi.mocked(epClientModule.epClient.getVotingRecords).mockResolvedValue({
        data: [],
        total: 500,
        limit: 50,
        offset: 999999,
        hasMore: false,
      });

      const result = await handleGetVotingRecords({ offset: 999999 });
      const data = JSON.parse(result.content[0]?.text ?? '{}') as { offset: number };
      expect(data.offset).toBe(999999);
    });

    it('should handle special characters in topic search', async () => {
      vi.mocked(epClientModule.epClient.getVotingRecords).mockResolvedValue({
        data: [],
        total: 0,
        limit: 50,
        offset: 0,
        hasMore: false,
      });
      const result = await handleGetVotingRecords({ topic: 'Climate & Energy Policy' });
      expect(vi.mocked(epClientModule.epClient.getVotingRecords)).toHaveBeenCalledWith(
        expect.objectContaining({ topic: 'Climate & Energy Policy' })
      );
      expect(result).toHaveProperty('content');
    });

    it('should handle network timeout error', async () => {
      vi.mocked(epClientModule.epClient.getVotingRecords).mockRejectedValue(
        new Error('Request timeout after 30000ms')
      );

      await expect(handleGetVotingRecords({}))
        .rejects.toThrow('Failed to retrieve voting records');
    });

    it('should handle rate limit error (429)', async () => {
      vi.mocked(epClientModule.epClient.getVotingRecords).mockRejectedValue(
        Object.assign(new Error('Too Many Requests'), { status: 429 })
      );

      await expect(handleGetVotingRecords({}))
        .rejects.toThrow('Failed to retrieve voting records');
    });

    it('should handle server error (500)', async () => {
      vi.mocked(epClientModule.epClient.getVotingRecords).mockRejectedValue(
        Object.assign(new Error('Internal Server Error'), { status: 500 })
      );

      await expect(handleGetVotingRecords({}))
        .rejects.toThrow('Failed to retrieve voting records');
    });
  });

  describe('Metadata', () => {
    it('should export tool metadata with correct name', () => {
      expect(getVotingRecordsToolMetadata).toHaveProperty('name', 'get_voting_records');
    });

    it('should export tool metadata with description containing data delay note', () => {
      expect(getVotingRecordsToolMetadata.description).toContain('delay');
      expect(getVotingRecordsToolMetadata.description).toContain('1-2 months');
    });
  });
});
