import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
  createChannel,
  getChannelById,
  getChannelStreamSelectors,
  getMusicVideoCreditsTemplates,
  updateChannel,
  type CreateChannelRequest,
  type UpdateChannelRequest
} from './channels';

const sampleChannel = {
  id: 5,
  number: '5',
  name: 'Cartoons',
  group: 'ChicoryTV',
  categories: '',
  ffmpegProfileId: 1,
  slugSeconds: null,
  logo: { path: '', contentType: '' },
  streamSelectorMode: 'Default',
  streamSelector: null,
  preferredAudioLanguageCode: null,
  preferredAudioTitle: null,
  playoutSource: 'Generated',
  playoutMode: 'Continuous',
  mirrorSourceChannelId: null,
  playoutOffset: null,
  streamingMode: 'TransportStreamHybrid',
  watermarkId: null,
  fallbackFillerId: null,
  playoutCount: 0,
  preferredSubtitleLanguageCode: null,
  subtitleMode: 'None',
  musicVideoCreditsMode: 'None',
  musicVideoCreditsTemplate: null,
  songVideoMode: 'Default',
  transcodeMode: 'OnDemand',
  idleBehavior: 'StopOnDisconnect',
  isEnabled: true,
  showInEpg: true
};

describe('getChannelById', () => {
  beforeEach(() => {
    window.localStorage.clear();
    vi.restoreAllMocks();
  });

  it('GETs the channel by id and returns the view model', async () => {
    const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(
      new Response(JSON.stringify(sampleChannel), {
        headers: { 'Content-Type': 'application/json' },
        status: 200
      })
    );

    await expect(getChannelById(5)).resolves.toMatchObject({ id: 5, name: 'Cartoons' });

    const [url, init] = fetchSpy.mock.calls[0];
    expect(url).toBe('/api/v1/channels/5');
    expect((init?.method ?? 'GET').toUpperCase()).toBe('GET');
  });

  it('rejects with the ApiError status on 404', async () => {
    vi.spyOn(window, 'fetch').mockResolvedValue(
      new Response(JSON.stringify({ status: 404, title: 'Not Found' }), {
        headers: { 'Content-Type': 'application/json' },
        status: 404
      })
    );

    await expect(getChannelById(999)).rejects.toMatchObject({ status: 404 });
  });
});

describe('updateChannel', () => {
  beforeEach(() => {
    window.localStorage.clear();
    vi.restoreAllMocks();
  });

  it('PUTs the request body to the channel id and returns the updated view model', async () => {
    const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(
      new Response(JSON.stringify({ ...sampleChannel, name: 'Renamed' }), {
        headers: { 'Content-Type': 'application/json' },
        status: 200
      })
    );

    const body = { ...sampleChannel, name: 'Renamed' } as unknown as UpdateChannelRequest;

    await expect(updateChannel(5, body)).resolves.toMatchObject({ name: 'Renamed' });

    const [url, init] = fetchSpy.mock.calls[0];
    expect(url).toBe('/api/v1/channels/5');
    expect((init?.method ?? '').toUpperCase()).toBe('PUT');
    expect(JSON.parse(init?.body as string)).toMatchObject({ name: 'Renamed', number: '5' });
  });
});

describe('createChannel', () => {
  beforeEach(() => {
    window.localStorage.clear();
    vi.restoreAllMocks();
  });

  it('POSTs the request body to /api/v1/channels and returns the created view model', async () => {
    const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(
      new Response(JSON.stringify(sampleChannel), {
        headers: { 'Content-Type': 'application/json' },
        status: 200
      })
    );

    const body = { ...sampleChannel, id: undefined } as unknown as CreateChannelRequest;

    await expect(createChannel(body)).resolves.toMatchObject({ id: 5 });

    const [url, init] = fetchSpy.mock.calls[0];
    expect(url).toBe('/api/v1/channels');
    expect((init?.method ?? '').toUpperCase()).toBe('POST');
  });
});

describe('getMusicVideoCreditsTemplates', () => {
  beforeEach(() => {
    window.localStorage.clear();
    vi.restoreAllMocks();
  });

  it('GETs /api/v1/channels/music-video-credits-templates', async () => {
    const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(
      new Response(JSON.stringify(['default']), {
        headers: { 'Content-Type': 'application/json' },
        status: 200
      })
    );

    await expect(getMusicVideoCreditsTemplates()).resolves.toEqual(['default']);

    const [url] = fetchSpy.mock.calls[0];
    expect(url).toBe('/api/v1/channels/music-video-credits-templates');
  });
});

describe('getChannelStreamSelectors', () => {
  beforeEach(() => {
    window.localStorage.clear();
    vi.restoreAllMocks();
  });

  it('GETs /api/v1/channels/stream-selectors', async () => {
    const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(
      new Response(JSON.stringify(['selector.py']), {
        headers: { 'Content-Type': 'application/json' },
        status: 200
      })
    );

    await expect(getChannelStreamSelectors()).resolves.toEqual(['selector.py']);

    const [url] = fetchSpy.mock.calls[0];
    expect(url).toBe('/api/v1/channels/stream-selectors');
  });
});
