/**
 * Integration tests for GET /api/history/recent
 *
 * Uses supertest against createApp() with vi.mock to stub historyFetcher
 * (avoids nock/ESM-CJS interop issues with axios) and nock for Emby auth.
 * Covers:
 *  - 401 when unauthenticated
 *  - Empty history response when arr returns no records
 *  - Filters out records whose eventType is not imported/failed
 *  - Returns imported and failed records for tagged series/movies
 *  - ?days= param is respected (default 7, capped at 90)
 *  - failureMessage included for admins on failed records
 */

import request from 'supertest';
import nock from 'nock';
import { beforeEach, afterEach } from 'vitest';
import { createRequire } from 'module';
import { createApp } from '../../server/app.js';

// Use createRequire to get the same CJS singleton cache instance that
// server/utils/historyFetcher.js and server/routes/history.js use via
// require('./cache'). A plain ESM `import cache from '...'` resolves
// to a different module identity under vitest's ESM runtime.
const require = createRequire(import.meta.url);
const cache = require('../../server/utils/cache.js');

const EMBY_BASE = 'https://emby.test';

process.env.EMBY_URL = EMBY_BASE;
process.env.SONARR_INSTANCES = JSON.stringify([
  { id: 'sonarr-1', name: 'Main Sonarr', url: 'https://sonarr.test', apiKey: 'sk' }
]);
process.env.RADARR_INSTANCES = JSON.stringify([
  { id: 'radarr-1', name: 'Main Radarr', url: 'https://radarr.test', apiKey: 'rk' }
]);

// --- Fixtures ---
const EMBY_AUTH = { AccessToken: 'tok', User: { Id: 'uid1', Name: 'alice' } };
const EMBY_USER = { Id: 'uid1', Name: 'alice', Policy: { IsAdministrator: false } };
const EMBY_ADMIN = { Id: 'uid2', Name: 'admin', Policy: { IsAdministrator: true } };
const EMBY_AUTH_ADMIN = { AccessToken: 'tok2', User: { Id: 'uid2', Name: 'admin' } };

// Sonarr tag: id 1 → 'alice'
const SONARR_TAGS = [{ id: 1, label: 'alice' }];

const SONARR_RECORD_IMPORTED = {
  id: 100,
  eventType: 'downloadFolderImported',
  sourceTitle: 'Show.S01E01.720p',
  date: new Date().toISOString(),
  quality: { quality: { name: '720p' } },
  series: { id: 10, title: 'My Show', titleSlug: 'my-show', tags: [1], images: [] },
  seriesId: 10
};

const SONARR_RECORD_FAILED = {
  id: 101,
  eventType: 'downloadFailed',
  sourceTitle: 'Show.S01E02.720p',
  date: new Date().toISOString(),
  quality: { quality: { name: '720p' } },
  data: { message: 'Not enough disk space' },
  series: { id: 11, title: 'Admin Show', titleSlug: 'admin-show', tags: [2], images: [] },
};

// Tag id 2 → 'admin' (used in the failed-import admin test)
const SONARR_TAGS_WITH_ADMIN = [{ id: 1, label: 'alice' }, { id: 2, label: 'admin' }];

const SONARR_RECORD_FAILED_ALICE = { // failed record tagged alice, for event-type filtering test
  id: 103,
  eventType: 'downloadFailed',
  sourceTitle: 'Show.S01E02.720p',
  date: new Date().toISOString(),
  quality: { quality: { name: '720p' } },
  data: { message: 'Disk full' },
  series: { id: 10, title: 'My Show', titleSlug: 'my-show', tags: [1], images: [] },
  seriesId: 10
};

const SONARR_RECORD_GRABBED = {
  id: 102,
  eventType: 'grabbed',
  sourceTitle: 'Show.S01E03.720p',
  date: new Date().toISOString(),
  series: { id: 10, title: 'My Show', titleSlug: 'my-show', tags: [1], images: [] },
  seriesId: 10
};

const RADARR_RECORD_IMPORTED = {
  id: 200,
  eventType: 'downloadFolderImported',
  sourceTitle: 'My.Movie.2024.1080p',
  date: new Date().toISOString(),
  quality: { quality: { name: '1080p' } },
  movie: { id: 20, title: 'My Movie', titleSlug: 'my-movie-2024', tags: [1], images: [] },
  movieId: 20
};

// --- Helpers ---
function interceptLogin(userBody = EMBY_USER, authBody = EMBY_AUTH) {
  nock(EMBY_BASE).post('/Users/authenticatebyname').reply(200, authBody);
  nock(EMBY_BASE).get(/\/Users\//).reply(200, userBody);
}

// Pre-seed the history cache keys that fetchSonarrHistory/fetchRadarrHistory check
// first, so they return without making any HTTP calls.
const CACHE_TTL = 60 * 60 * 1000; // 1 hour — won't expire during a test run

function setHistory(sonarrRecords = [], radarrRecords = []) {
  cache.set('history:sonarr', sonarrRecords.map(r => ({
    ...r,
    _instanceName: 'Main Sonarr',
    series: r.series ? { ...r.series, _instanceUrl: 'https://sonarr.test', _instanceName: 'Main Sonarr' } : undefined
  })), CACHE_TTL);
  cache.set('history:radarr', radarrRecords.map(r => ({
    ...r,
    _instanceName: 'Main Radarr',
    movie: r.movie ? { ...r.movie, _instanceUrl: 'https://radarr.test', _instanceName: 'Main Radarr' } : undefined
  })), CACHE_TTL);
}

async function loginAs(app, userBody = EMBY_USER, authBody = EMBY_AUTH) {
  interceptLogin(userBody, authBody);
  const res = await request(app)
    .post('/api/auth/login')
    .send({ username: userBody.Name, password: 'pw' });
  const cookies = res.headers['set-cookie'];
  const csrf = res.body.csrfToken;
  return { cookies, csrf };
}

beforeEach(() => {
  // Clear history caches so each test controls its own data
  cache.invalidate('history:sonarr');
  cache.invalidate('history:radarr');
  // Default: empty history
  setHistory([], []);
  // Seed poll tag caches so the route can resolve tags
  cache.set('poll:sonarr-tags', [{ data: SONARR_TAGS }], 60000);
  cache.set('poll:radarr-tags', [], 60000);
});

afterEach(() => {
  nock.cleanAll();
  cache.invalidate('history:sonarr');
  cache.invalidate('history:radarr');
});

describe('GET /api/history/recent', () => {
  describe('authentication', () => {
    it('returns 401 when not logged in', async () => {
      const app = createApp({ skipRateLimits: true });
      const res = await request(app).get('/api/history/recent');
      expect(res.status).toBe(401);
    });
  });

  describe('empty history', () => {
    it('returns empty array when arr returns no records', async () => {
      const app = createApp({ skipRateLimits: true });
      const { cookies } = await loginAs(app);
      const res = await request(app)
        .get('/api/history/recent')
        .set('Cookie', cookies);
      expect(res.status).toBe(200);
      expect(res.body.history).toEqual([]);
      expect(res.body.days).toBe(7);
    });
  });

  describe('event type filtering', () => {
    it('includes imported and failed records, excludes grabbed', async () => {
      const app = createApp({ skipRateLimits: true });
      setHistory(
        [SONARR_RECORD_IMPORTED, SONARR_RECORD_FAILED_ALICE, SONARR_RECORD_GRABBED],
        []
      );
      const { cookies } = await loginAs(app);
      const res = await request(app)
        .get('/api/history/recent')
        .set('Cookie', cookies);
      expect(res.status).toBe(200);
      const outcomes = res.body.history.map(h => h.outcome);
      expect(outcomes).toContain('imported');
      expect(outcomes).toContain('failed');
      expect(res.body.history).toHaveLength(2);
    });
  });

  describe('tag filtering', () => {
    it('only returns records tagged for the current user', async () => {
      const app = createApp({ skipRateLimits: true });
      setHistory([SONARR_RECORD_IMPORTED], []);
      const { cookies } = await loginAs(app);
      const res = await request(app)
        .get('/api/history/recent')
        .set('Cookie', cookies);
      expect(res.status).toBe(200);
      expect(res.body.history).toHaveLength(1);
      expect(res.body.history[0].seriesName).toBe('My Show');
      expect(res.body.history[0].outcome).toBe('imported');
      expect(res.body.history[0].quality).toBe('720p');
    });

    it('excludes records tagged for a different user', async () => {
      const app = createApp({ skipRateLimits: true });
      const bobAuth = { AccessToken: 'tok3', User: { Id: 'uid3', Name: 'bob' } };
      const bobUser = { Id: 'uid3', Name: 'bob', Policy: { IsAdministrator: false } };
      setHistory([SONARR_RECORD_IMPORTED], []);
      const { cookies } = await loginAs(app, bobUser, bobAuth);
      const res = await request(app)
        .get('/api/history/recent')
        .set('Cookie', cookies);
      expect(res.status).toBe(200);
      expect(res.body.history).toHaveLength(0);
    });
  });

  describe('radarr records', () => {
    it('returns movie history items', async () => {
      const app = createApp({ skipRateLimits: true });
      cache.set('poll:radarr-tags', [{ id: 1, label: 'alice' }], 60000);
      setHistory([], [RADARR_RECORD_IMPORTED]);
      const { cookies } = await loginAs(app);
      const res = await request(app)
        .get('/api/history/recent')
        .set('Cookie', cookies);
      expect(res.status).toBe(200);
      expect(res.body.history).toHaveLength(1);
      expect(res.body.history[0].type).toBe('movie');
      expect(res.body.history[0].movieName).toBe('My Movie');
    });
  });

  describe('?days parameter', () => {
    it('uses custom days value', async () => {
      const app = createApp({ skipRateLimits: true });
      const { cookies } = await loginAs(app);
      const res = await request(app)
        .get('/api/history/recent?days=14')
        .set('Cookie', cookies);
      expect(res.status).toBe(200);
      expect(res.body.days).toBe(14);
    });

    it('caps days at 90', async () => {
      const app = createApp({ skipRateLimits: true });
      const { cookies } = await loginAs(app);
      const res = await request(app)
        .get('/api/history/recent?days=999')
        .set('Cookie', cookies);
      expect(res.status).toBe(200);
      expect(res.body.days).toBe(7); // falls back to default when > 90
    });
  });

  describe('failed import details', () => {
    it('includes failureMessage for admin on failed records', async () => {
      const app = createApp({ skipRateLimits: true });
      cache.set('poll:sonarr-tags', [{ data: SONARR_TAGS_WITH_ADMIN }], 60000);
      setHistory([SONARR_RECORD_FAILED], []);
      const { cookies } = await loginAs(app, EMBY_ADMIN, EMBY_AUTH_ADMIN);
      const res = await request(app)
        .get('/api/history/recent')
        .set('Cookie', cookies);
      expect(res.status).toBe(200);
      const failed = res.body.history.find(h => h.outcome === 'failed');
      expect(failed).toBeDefined();
      expect(failed.failureMessage).toBe('Not enough disk space');
    });
  });

  describe('response shape', () => {
    it('returns correct top-level fields', async () => {
      const app = createApp({ skipRateLimits: true });
      const { cookies } = await loginAs(app);
      const res = await request(app)
        .get('/api/history/recent')
        .set('Cookie', cookies);
      expect(res.status).toBe(200);
      expect(res.body).toHaveProperty('user');
      expect(res.body).toHaveProperty('isAdmin');
      expect(res.body).toHaveProperty('days');
      expect(res.body).toHaveProperty('history');
      expect(Array.isArray(res.body.history)).toBe(true);
    });
  });
});
