/**
 * Tests for get_adopted_texts MCP tool
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { handleGetAdoptedTexts, getAdoptedTextsToolMetadata } from './getAdoptedTexts.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: {
    getAdoptedTexts: vi.fn(),
    getAdoptedTextById: vi.fn()
  }
}));

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

describe('get_adopted_texts Tool', () => {
  beforeEach(() => {
    // Setup default mock implementation
    vi.mocked(epClientModule.epClient.getAdoptedTexts).mockResolvedValue({
      data: [
        {
          id: 'TA-9-2024-0001',
          title: 'European Artificial Intelligence Act',
          reference: 'P9_TA(2024)0001',
          type: 'LEGISLATIVE_RESOLUTION',
          dateAdopted: '2024-03-13',
          procedureReference: '2023/0123(COD)',
          subjectMatter: 'Internal market'
        }
      ],
      total: 1,
      limit: 50,
      offset: 0,
      hasMore: false
    });
  });

  describe('Input Validation', () => {
    it('should accept empty arguments and apply defaults', async () => {
      const result = await handleGetAdoptedTexts({});
      expect(result).toHaveProperty('content');
      expect(result.content[0]?.type).toBe('text');
    });

    it('should accept valid year filter', async () => {
      const result = await handleGetAdoptedTexts({ year: 2024 });
      expect(result).toHaveProperty('content');
    });

    it('should accept valid limit and offset', async () => {
      const result = await handleGetAdoptedTexts({ limit: 25, offset: 10 });
      expect(result).toHaveProperty('content');
    });

    it('should reject year below minimum', async () => {
      await expect(handleGetAdoptedTexts({ year: 1989 })).rejects.toThrow();
    });

    it('should reject year above maximum', async () => {
      await expect(handleGetAdoptedTexts({ year: 2041 })).rejects.toThrow();
    });

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

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

    it('should reject negative offset', async () => {
      await expect(handleGetAdoptedTexts({ offset: -1 })).rejects.toThrow();
    });
  });

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

    it('should include mock adopted text with id and title in payload', async () => {
      const result = await handleGetAdoptedTexts({});
      const parsed = expectValidPaginatedMCPResponse(result);
      const first = parsed.data[0] as Record<string, unknown> | undefined;
      expect(first).toBeDefined();
      expect(first?.['id']).toBe('TA-9-2024-0001');
      expect(first?.['title']).toBe('European Artificial Intelligence Act');
    });

    it('should return paginated response with adopted text data', async () => {
      const result = await handleGetAdoptedTexts({});
      expectValidPaginatedMCPResponse(result);
    });
  });

  describe('Error Handling', () => {
    it('should propagate API client errors', async () => {
      vi.mocked(epClientModule.epClient.getAdoptedTexts)
        .mockRejectedValueOnce(new Error('Connection refused'));

      await expect(handleGetAdoptedTexts({})).rejects.toThrow('Failed to retrieve adopted texts');
    });

    it('should propagate schema validation errors for invalid input', async () => {
      await expect(handleGetAdoptedTexts({ year: 'invalid' })).rejects.toThrow();
    });
  });

  describe('Client Invocation', () => {
    it('should pass year filter to client when provided', async () => {
      await handleGetAdoptedTexts({ year: 2024, limit: 10 });

      expect(epClientModule.epClient.getAdoptedTexts).toHaveBeenCalledWith(
        expect.objectContaining({
          year: 2024,
          limit: 10,
          offset: 0
        })
      );
    });

    it('should not pass undefined year to client', async () => {
      await handleGetAdoptedTexts({ limit: 20 });

      const callArgs = vi.mocked(epClientModule.epClient.getAdoptedTexts).mock.calls[0]?.[0];
      expect(callArgs).not.toHaveProperty('year');
    });
  });

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

    it('should export tool metadata with description', () => {
      expect(getAdoptedTextsToolMetadata).toHaveProperty('description');
      expect(getAdoptedTextsToolMetadata.description.length).toBeGreaterThan(0);
    });

    it('should export tool metadata with inputSchema', () => {
      expect(getAdoptedTextsToolMetadata).toHaveProperty('inputSchema');
      expect(getAdoptedTextsToolMetadata.inputSchema).toHaveProperty('type', 'object');
      expect(getAdoptedTextsToolMetadata.inputSchema).toHaveProperty('properties');
    });

    it('should define year, limit, offset in schema', () => {
      const props = getAdoptedTextsToolMetadata.inputSchema.properties;
      expect(props).toHaveProperty('year');
      expect(props).toHaveProperty('limit');
      expect(props).toHaveProperty('offset');
    });
  });

  describe('docId lookup branch', () => {
    it('should call getAdoptedTextById when docId is provided', async () => {
      const mockText = {
        id: 'TA-9-2024-0001',
        title: 'AI Act',
        date: '2024-03-13',
        type: 'TA',
        procedure: 'COD/2021/0106',
        documentReference: 'P9_TA(2024)0138'
      };
      vi.mocked(epClientModule.epClient.getAdoptedTextById).mockResolvedValue(mockText);

      const result = await handleGetAdoptedTexts({ docId: 'TA-9-2024-0001' });
      expect(result.content[0].type).toBe('text');
      const parsed = JSON.parse(result.content[0].text) as { id: string };
      expect(parsed.id).toBe('TA-9-2024-0001');
    });

    it('should translate a content-pending APIError(404) from the client to a non-retryable UPSTREAM_404 ToolError', async () => {
      const { APIError } = await import('../clients/ep/baseClient.js');
      vi.mocked(epClientModule.epClient.getAdoptedTextById).mockRejectedValueOnce(
        new APIError(
          'Adopted text "TA-10-2026-0099": document indexed but content not yet available',
          404
        )
      );

      try {
        await handleGetAdoptedTexts({ docId: 'TA-10-2026-0099' });
        expect.fail('Expected handleGetAdoptedTexts to throw');
      } catch (error: unknown) {
        const { ToolError } = await import('./shared/errors.js');
        expect(error).toBeInstanceOf(ToolError);
        const toolError = error as InstanceType<typeof ToolError>;
        expect(toolError.errorCode).toBe('UPSTREAM_404');
        expect(toolError.httpStatus).toBe(404);
        expect(toolError.isRetryable).toBe(false);
        expect(toolError.message).toContain('TA-10-2026-0099');
        expect(toolError.message).toContain('content not yet available');
      }
    });

    it('should append docId to UPSTREAM_404 message when upstream APIError(404) message is generic', async () => {
      const { APIError } = await import('../clients/ep/baseClient.js');
      // BaseEPClient surfaces raw HTTP 404s as a generic message that does
      // not include the requested docId — the tool layer must append it so
      // clients can identify what was not found.
      vi.mocked(epClientModule.epClient.getAdoptedTextById).mockRejectedValueOnce(
        new APIError('EP API request failed: Not Found', 404)
      );

      try {
        await handleGetAdoptedTexts({ docId: 'TA-9-2024-9999' });
        expect.fail('Expected handleGetAdoptedTexts to throw');
      } catch (error: unknown) {
        const { ToolError } = await import('./shared/errors.js');
        expect(error).toBeInstanceOf(ToolError);
        const toolError = error as InstanceType<typeof ToolError>;
        expect(toolError.errorCode).toBe('UPSTREAM_404');
        expect(toolError.message).toContain('TA-9-2024-9999');
        expect(toolError.message).toContain('Not Found');
      }
    });
  });
});
