import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { ReadResourceResult, TextResourceContents } from '@modelcontextprotocol/sdk/types.js';
import { startMcpServer, McpTestContext } from '../../utils/mcp-test-helpers';
import path from 'path';

// Helper function to parse JSON safely
function parseJsonSafely(text: string | undefined): unknown {
  if (text === undefined) {
    throw new Error('Received undefined text for JSON parsing');
  }
  try {
    return JSON.parse(text);
  } catch (e) {
    console.error('Failed to parse JSON:', text);
    throw new Error(`Invalid JSON received: ${e instanceof Error ? e.message : String(e)}`);
  }
}

// Type guard to check if content is TextResourceContents
function hasTextContent(
  content: ReadResourceResult['contents'][0]
): content is TextResourceContents {
  return typeof (content as TextResourceContents).text === 'string';
}

describe('E2E Tests for Spec Loading Scenarios', () => {
  let testContext: McpTestContext | null = null; // Allow null for cleanup
  let client: Client | null = null; // Allow null

  // Helper to setup client for tests, allowing different spec paths
  async function setup(specPathOrUrl: string): Promise<void> {
    // Cleanup previous context if exists
    if (testContext) {
      await testContext.cleanup();
      testContext = null;
      client = null;
    }
    try {
      testContext = await startMcpServer(specPathOrUrl, { outputFormat: 'json' });
      client = testContext.client;
    } catch (error) {
      // Explicitly convert error to string for logging
      const errorMsg = error instanceof Error ? error.message : String(error);
      console.warn(`Skipping tests for ${specPathOrUrl} due to setup error: ${errorMsg}`);
      testContext = null; // Ensure cleanup doesn't run on failed setup
      client = null; // Ensure tests are skipped
    }
  }

  afterEach(async () => {
    await testContext?.cleanup();
    testContext = null;
    client = null;
  });

  // Helper to read resource and perform basic checks
  async function readResourceAndCheck(uri: string): Promise<ReadResourceResult['contents'][0]> {
    if (!client) throw new Error('Client not initialized, skipping test.');
    const result = await client.readResource({ uri });
    expect(result.contents).toHaveLength(1);
    const content = result.contents[0];
    expect(content.uri).toBe(uri);
    return content;
  }

  // Helper to read resource and check for text/plain list content
  async function checkTextListResponse(uri: string, expectedSubstrings: string[]): Promise<string> {
    const content = await readResourceAndCheck(uri);
    expect(content.mimeType).toBe('text/plain');
    expect(content.isError).toBeFalsy();
    if (!hasTextContent(content)) throw new Error('Expected text content');
    for (const sub of expectedSubstrings) {
      expect(content.text).toContain(sub);
    }
    return content.text;
  }

  // Helper to read resource and check for JSON detail content
  async function checkJsonDetailResponse(uri: string, expectedObject: object): Promise<unknown> {
    const content = await readResourceAndCheck(uri);
    expect(content.mimeType).toBe('application/json');
    expect(content.isError).toBeFalsy();
    if (!hasTextContent(content)) throw new Error('Expected text content');
    const data = parseJsonSafely(content.text);
    expect(data).toMatchObject(expectedObject);
    return data;
  }

  // --- Tests for Local Swagger v2.0 Spec ---
  describe('Local Swagger v2.0 Spec (sample-v2-api.json)', () => {
    const v2SpecPath = path.resolve(__dirname, '../../fixtures/sample-v2-api.json');

    beforeAll(async () => await setup(v2SpecPath)); // Use beforeAll for this block

    it('should retrieve the converted "info" field', async () => {
      if (!client) return; // Skip if setup failed
      await checkJsonDetailResponse('openapi://info', {
        title: 'Simple Swagger 2.0 API',
        version: '1.0.0',
      });
    });

    it('should retrieve the converted "paths" list', async () => {
      if (!client) return; // Skip if setup failed
      await checkTextListResponse('openapi://paths', [
        'Hint:',
        'GET /v2/ping', // Note the basePath is included
      ]);
    });

    it('should retrieve the converted "components" list', async () => {
      if (!client) return; // Skip if setup failed
      await checkTextListResponse('openapi://components', [
        'Available Component Types:',
        '- schemas',
        "Hint: Use 'openapi://components/{type}'",
      ]);
    });

    it('should get details for converted schema Pong', async () => {
      if (!client) return; // Skip if setup failed
      await checkJsonDetailResponse('openapi://components/schemas/Pong', {
        type: 'object',
        properties: { message: { type: 'string', example: 'pong' } },
      });
    });
  });

  // --- Tests for Remote OpenAPI v3.0 Spec (Petstore) ---
  // Increase timeout for remote fetch
  jest.setTimeout(20000); // 20 seconds

  describe('Remote OpenAPI v3.0 Spec (Petstore)', () => {
    const petstoreUrl = 'https://petstore3.swagger.io/api/v3/openapi.json';

    beforeAll(async () => await setup(petstoreUrl)); // Use beforeAll for this block

    it('should retrieve the "info" field from Petstore', async () => {
      if (!client) return; // Skip if setup failed
      await checkJsonDetailResponse('openapi://info', {
        title: 'Swagger Petstore - OpenAPI 3.0',
        // version might change, so don't assert exact value
      });
    });

    it('should retrieve the "paths" list from Petstore', async () => {
      if (!client) return; // Skip if setup failed
      // Check for a known path
      await checkTextListResponse('openapi://paths', ['/pet/{petId}']);
    });

    it('should retrieve the "components" list from Petstore', async () => {
      if (!client) return; // Skip if setup failed
      // Check for known component types
      await checkTextListResponse('openapi://components', [
        '- schemas',
        '- requestBodies',
        '- securitySchemes',
      ]);
    });

    it('should get details for schema Pet from Petstore', async () => {
      if (!client) return; // Skip if setup failed
      await checkJsonDetailResponse('openapi://components/schemas/Pet', {
        required: ['name', 'photoUrls'],
        type: 'object',
        // Check a known property
        properties: { id: { type: 'integer', format: 'int64' } },
      });
    });
  });
});
