import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
import { mkdtempSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { Cache } from '../src/services/cache.ts';
import type { TextFragment } from '../src/types.ts';

let tmpDir: string;
let cache: Cache;

beforeEach(() => {
  tmpDir = mkdtempSync(join(tmpdir(), 'qa-cache-test-'));
  cache = new Cache(tmpDir);
});

afterEach(() => {
  rmSync(tmpDir, { recursive: true, force: true });
});

describe('cache — chapters', () => {
  test('returns null on a cold cache', () => {
    expect(cache.tryGetChapters(1, 0, 5)).toBeNull();
  });

  test('returns null when a chapter in range is missing', () => {
    cache.saveChapters(1, [
      { index: 0, content: 'Chapter zero content here' },
      // index 1 is intentionally missing
      { index: 2, content: 'Chapter two content here' },
    ]);
    expect(cache.tryGetChapters(1, 0, 2)).toBeNull();
  });

  test('returns null for invalid range (min > max)', () => {
    expect(cache.tryGetChapters(1, 5, 2)).toBeNull();
  });

  test('saves and retrieves plain-text chapters', () => {
    cache.saveChapters(1, [
      { index: 0, content: 'Line one\nLine two' },
      { index: 1, content: 'Chapter one content here' },
    ]);
    const view = cache.tryGetChapters(1, 0, 1);
    expect(view).not.toBeNull();
    expect(view!.id).toBe(1);
    expect(view!.min).toBe(0);
    expect(view!.max).toBe(1);
    expect(view!.chapters).toHaveLength(2);
    expect(view!.chapters[0]!.hasStyle).toBe(false);
  });

  test('detects styled (JSON fragment) chapters', () => {
    const fragments: TextFragment[] = [{ body: 'Hello world', style: undefined }];
    cache.saveChapters(1, [{ index: 0, content: JSON.stringify(fragments) }]);
    const view = cache.tryGetChapters(1, 0, 0);
    expect(view).not.toBeNull();
    expect(view!.chapters[0]!.hasStyle).toBe(true);
  });

  test('returns null when a chapter file is too short', () => {
    cache.saveChapters(1, [{ index: 0, content: 'hi' }]); // < 10 bytes
    expect(cache.tryGetChapters(1, 0, 0)).toBeNull();
  });

  test('handles single-chapter range', () => {
    cache.saveChapters(42, [{ index: 7, content: 'Single chapter content ok' }]);
    const view = cache.tryGetChapters(42, 7, 7);
    expect(view).not.toBeNull();
    expect(view!.chapters).toHaveLength(1);
  });
});

describe('cache — TOC', () => {
  test('returns null on a cold cache', () => {
    expect(cache.tryGetToc(1)).toBeNull();
  });

  test('round-trips a TOC', () => {
    const urls = [
      'https://example.com/chapter-1',
      'https://example.com/chapter-2',
      'https://example.com/chapter-3',
    ];
    cache.saveToc(1, urls);
    expect(cache.tryGetToc(1)).toEqual(urls);
  });

  test('returns null for an empty TOC file', () => {
    cache.saveToc(1, []);
    expect(cache.tryGetToc(1)).toBeNull();
  });

  test('overwrites an existing TOC on re-save', () => {
    cache.saveToc(1, ['https://example.com/old']);
    cache.saveToc(1, ['https://example.com/new-1', 'https://example.com/new-2']);
    expect(cache.tryGetToc(1)).toEqual([
      'https://example.com/new-1',
      'https://example.com/new-2',
    ]);
  });
});
