/**
 * Unit tests for server/utils/historyFetcher.js
 *
 * Covers:
 *  - classifySonarrEvent / classifyRadarrEvent event classification
 *  - fetchSonarrHistory / fetchRadarrHistory: successful fetch, cache hit, per-instance errors
 *  - invalidateHistoryCache
 */

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import nock from 'nock';

// Env must be set before importing modules that read it at load time
process.env.SONARR_INSTANCES = JSON.stringify([
  { id: 'sonarr-1', name: 'Main Sonarr', url: 'https://sonarr.test', apiKey: 'sonarr-key' }
]);
process.env.RADARR_INSTANCES = JSON.stringify([
  { id: 'radarr-1', name: 'Main Radarr', url: 'https://radarr.test', apiKey: 'radarr-key' }
]);

const { classifySonarrEvent, classifyRadarrEvent, fetchSonarrHistory, fetchRadarrHistory, invalidateHistoryCache } =
  await import('../../server/utils/historyFetcher.js');

const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);

afterEach(() => {
  nock.cleanAll();
  invalidateHistoryCache();
});

describe('classifySonarrEvent', () => {
  it('returns imported for downloadFolderImported', () => {
    expect(classifySonarrEvent('downloadFolderImported')).toBe('imported');
  });
  it('returns imported for downloadImported', () => {
    expect(classifySonarrEvent('downloadImported')).toBe('imported');
  });
  it('returns failed for downloadFailed', () => {
    expect(classifySonarrEvent('downloadFailed')).toBe('failed');
  });
  it('returns failed for importFailed', () => {
    expect(classifySonarrEvent('importFailed')).toBe('failed');
  });
  it('returns other for grabbed', () => {
    expect(classifySonarrEvent('grabbed')).toBe('other');
  });
  it('returns other for unknown event', () => {
    expect(classifySonarrEvent('someFutureEvent')).toBe('other');
  });
});

describe('classifyRadarrEvent', () => {
  it('returns imported for downloadFolderImported', () => {
    expect(classifyRadarrEvent('downloadFolderImported')).toBe('imported');
  });
  it('returns failed for downloadFailed', () => {
    expect(classifyRadarrEvent('downloadFailed')).toBe('failed');
  });
  it('returns other for grabbed', () => {
    expect(classifyRadarrEvent('grabbed')).toBe('other');
  });
});

describe('fetchSonarrHistory', () => {
  const mockRecords = [
    {
      id: 1,
      eventType: 'downloadFolderImported',
      sourceTitle: 'Show.S01E01',
      date: new Date().toISOString(),
      series: { id: 10, title: 'My Show', titleSlug: 'my-show', tags: [] },
      seriesId: 10
    }
  ];

  it('fetches records and tags them with _instanceUrl and _instanceName', async () => {
    nock('https://sonarr.test')
      .get('/api/v3/history')
      .query(true)
      .reply(200, { records: mockRecords });

    const result = await fetchSonarrHistory(since);
    expect(result).toHaveLength(1);
    expect(result[0].series._instanceUrl).toBe('https://sonarr.test');
    expect(result[0].series._instanceName).toBe('Main Sonarr');
    expect(result[0]._instanceName).toBe('Main Sonarr');
  });

  it('returns cached data on second call without making a new HTTP request', async () => {
    nock('https://sonarr.test')
      .get('/api/v3/history')
      .query(true)
      .reply(200, { records: mockRecords });

    const first = await fetchSonarrHistory(since);
    // Second call — nock would throw if a second request was made
    const second = await fetchSonarrHistory(since);
    expect(second).toEqual(first);
  });

  it('returns empty array and does not throw when instance errors', async () => {
    nock('https://sonarr.test')
      .get('/api/v3/history')
      .query(true)
      .replyWithError('ECONNREFUSED');

    const result = await fetchSonarrHistory(since);
    expect(result).toEqual([]);
  });

  it('handles missing records key gracefully', async () => {
    nock('https://sonarr.test')
      .get('/api/v3/history')
      .query(true)
      .reply(200, {});

    const result = await fetchSonarrHistory(since);
    expect(result).toEqual([]);
  });
});

describe('fetchRadarrHistory', () => {
  const mockRecords = [
    {
      id: 2,
      eventType: 'downloadFolderImported',
      sourceTitle: 'My.Movie.2024',
      date: new Date().toISOString(),
      movie: { id: 20, title: 'My Movie', titleSlug: 'my-movie-2024', tags: [] },
      movieId: 20
    }
  ];

  it('fetches records and tags them with _instanceUrl and _instanceName', async () => {
    nock('https://radarr.test')
      .get('/api/v3/history')
      .query(true)
      .reply(200, { records: mockRecords });

    const result = await fetchRadarrHistory(since);
    expect(result).toHaveLength(1);
    expect(result[0].movie._instanceUrl).toBe('https://radarr.test');
    expect(result[0].movie._instanceName).toBe('Main Radarr');
  });

  it('returns empty array on network error', async () => {
    nock('https://radarr.test')
      .get('/api/v3/history')
      .query(true)
      .replyWithError('timeout');

    const result = await fetchRadarrHistory(since);
    expect(result).toEqual([]);
  });
});

describe('invalidateHistoryCache', () => {
  it('forces a fresh fetch after invalidation', async () => {
    nock('https://sonarr.test')
      .get('/api/v3/history')
      .query(true)
      .reply(200, { records: [] });

    await fetchSonarrHistory(since);
    invalidateHistoryCache();

    // Should make a second HTTP request — nock will satisfy it
    nock('https://sonarr.test')
      .get('/api/v3/history')
      .query(true)
      .reply(200, { records: [] });

    const result = await fetchSonarrHistory(since);
    expect(result).toEqual([]);
    expect(nock.isDone()).toBe(true);
  });
});
