import { AsyncAPISchema } from '@mintlify/common/asyncapi';
import fs from 'fs/promises';
import yaml from 'js-yaml';
import { describe, it, expect, beforeEach, vi } from 'vitest';

import { getAsyncApiDefinition } from '../src/asyncapi/getAsyncApiDefinition.js';

vi.mock('fs/promises');
vi.mock('node-fetch');

const mockAsyncApiDoc: AsyncAPISchema = {
  asyncapi: '3.0.0',
  info: {
    title: 'Test API',
    version: '1.0.0',
  },
  channels: {},
  components: {},
};

describe('getAsyncApiDefinition', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  it('should load AsyncAPI doc from a local file path', async () => {
    const mockYaml = yaml.dump(mockAsyncApiDoc);
    vi.mocked(fs.readFile).mockResolvedValue(mockYaml);

    const result = await getAsyncApiDefinition('test.yaml');

    expect(fs.readFile).toHaveBeenCalledWith(expect.stringContaining('test.yaml'), 'utf-8');
    expect(result).toEqual({ document: mockAsyncApiDoc, isUrl: false });
  });

  it('should accept AsyncAPI document directly', async () => {
    const result = await getAsyncApiDefinition(mockAsyncApiDoc);
    expect(result).toEqual({ document: mockAsyncApiDoc, isUrl: false });
  });

  it('should fetch yaml AsyncAPI doc from URL', async () => {
    const mockYaml = yaml.dump(mockAsyncApiDoc);
    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      text: () => Promise.resolve(mockYaml),
    });

    const url = new URL('https://example.com/asyncapi.yaml');
    const result = await getAsyncApiDefinition(url);

    expect(fetch).toHaveBeenCalledWith(url);
    expect(result).toEqual({ document: mockAsyncApiDoc, isUrl: true });
  });

  it('should fetch and parse valid JSON AsyncAPI doc from URL', async () => {
    const mockJson = JSON.stringify(mockAsyncApiDoc);
    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      status: 200,
      text: () => Promise.resolve(mockJson),
    });

    const url = new URL('https://example.com/asyncapi.yaml');
    const result = await getAsyncApiDefinition(url);

    expect(fetch).toHaveBeenCalledWith(url);
    expect(result).toEqual({ document: mockAsyncApiDoc, isUrl: true });
  });

  it('should fetch AsyncAPI doc from URL string', async () => {
    const mockYaml = yaml.dump(mockAsyncApiDoc);
    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      status: 200,
      text: () => Promise.resolve(mockYaml),
    });

    const urlString = 'https://example.com/asyncapi.yaml';
    const result = await getAsyncApiDefinition(urlString);

    expect(fetch).toHaveBeenCalledWith(new URL(urlString));
    expect(result).toEqual({ document: mockAsyncApiDoc, isUrl: true });
  });

  it('should throw error when local file read fails', async () => {
    vi.mocked(fs.readFile).mockRejectedValue(new Error('File read error'));

    await expect(getAsyncApiDefinition('test.yaml')).rejects.toThrow('File read error');
  });

  it('should throw error with URL and status code when fetch fails', async () => {
    global.fetch = vi.fn().mockResolvedValue({
      ok: false,
      status: 404,
      statusText: 'Not Found',
    });

    const urlString = 'https://mycoolwebsocketschema/asyncapi.doesnotexist';

    await expect(getAsyncApiDefinition(urlString)).rejects.toThrow(
      `${urlString} - failed to retrieve AsyncAPI file from source - : 404 Not Found`
    );

    expect(fetch).toHaveBeenCalledWith(new URL(urlString));
  });

  it('should throw error when HTTP URL is provided', async () => {
    const httpUrl = new URL('http://example.com/asyncapi.yaml');
    await expect(getAsyncApiDefinition(httpUrl)).rejects.toThrow(
      'Only HTTPS URLs are supported. Please provide an HTTPS URL'
    );
  });

  it('should throw error when HTTP URL string is provided', async () => {
    const httpUrlString = 'http://example.com/asyncapi.yaml';
    await expect(getAsyncApiDefinition(httpUrlString)).rejects.toThrow(
      'Only HTTPS URLs are supported. Please provide an HTTPS URL'
    );
  });

  it('should throw error when non-HTTPS URL string is provided', async () => {
    const httpUrlString = 'ftp://example.com/asyncapi.yaml';
    await expect(getAsyncApiDefinition(httpUrlString)).rejects.toThrow(
      'Only HTTPS URLs are supported. Please provide an HTTPS URL'
    );
  });

  it('should throw error when URL response is invalid YAML', async () => {
    global.fetch = vi.fn().mockResolvedValue({
      text: () => Promise.resolve('invalid: yaml: content'),
    });

    await expect(getAsyncApiDefinition('https://example.com/asyncapi.yaml')).rejects.toThrow();
  });

  it('should throw error when URL response is invalid JSON', async () => {
    global.fetch = vi.fn().mockResolvedValue({
      text: () => Promise.resolve('{"invalid": "yaml", "invalid": "content"}'),
    });

    await expect(getAsyncApiDefinition('https://example.com/asyncapi.json')).rejects.toThrow();
  });

  it('should throw error when local file contains invalid YAML', async () => {
    vi.mocked(fs.readFile).mockResolvedValue('invalid: yaml: content');

    await expect(getAsyncApiDefinition('test.yaml')).rejects.toThrow();
  });

  it('should throw error when local file contains invalid JSON', async () => {
    vi.mocked(fs.readFile).mockResolvedValue('{"invalid": "yaml", "invalid": "content"}');

    await expect(getAsyncApiDefinition('test.json')).rejects.toThrow();
  });
});
