/**
 * Component: Search Ebook Processor Tests
 * Documentation: documentation/integrations/ebook-sidecar.md
 */

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createPrismaMock } from '../helpers/prisma';

const prismaMock = createPrismaMock();

const configServiceMock = vi.hoisted(() => ({
  get: vi.fn(),
  getAudibleRegion: vi.fn().mockResolvedValue('us'),
}));

const jobQueueMock = vi.hoisted(() => ({
  addStartDirectDownloadJob: vi.fn(() => Promise.resolve()),
}));

const ebookScraperMock = vi.hoisted(() => ({
  searchByAsin: vi.fn(),
  searchByTitle: vi.fn(),
  getSlowDownloadLinks: vi.fn(),
}));

vi.mock('@/lib/db', () => ({
  prisma: prismaMock,
}));

vi.mock('@/lib/services/config.service', () => ({
  getConfigService: () => configServiceMock,
}));

vi.mock('@/lib/services/job-queue.service', () => ({
  getJobQueueService: () => jobQueueMock,
}));

vi.mock('@/lib/services/ebook-scraper', () => ebookScraperMock);

describe('processSearchEbook', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    configServiceMock.getAudibleRegion.mockResolvedValue('us');
    configServiceMock.get.mockImplementation(async (key: string) => {
      if (key === 'ebook_sidecar_preferred_format') return 'epub';
      if (key === 'ebook_sidecar_base_url') return 'https://annas-archive.gl';
      if (key === 'ebook_annas_archive_enabled') return 'true';
      if (key === 'ebook_indexer_search_enabled') return 'false';
      return null;
    });
  });

  it('searches by ASIN when available and triggers download', async () => {
    prismaMock.request.update.mockResolvedValue({});
    prismaMock.downloadHistory.create.mockResolvedValue({ id: 'dh-1' });
    prismaMock.downloadHistory.update.mockResolvedValue({});

    ebookScraperMock.searchByAsin.mockResolvedValue('abc123md5');
    ebookScraperMock.getSlowDownloadLinks.mockResolvedValue([
      'https://slow1.example.com/abc123',
      'https://slow2.example.com/abc123',
    ]);

    const { processSearchEbook } = await import('@/lib/processors/search-ebook.processor');

    const result = await processSearchEbook({
      requestId: 'req-1',
      audiobook: {
        id: 'ab-1',
        title: 'Test Book',
        author: 'Test Author',
        asin: 'B001ASIN',
      },
      jobId: 'job-1',
    });

    expect(result.success).toBe(true);
    expect(result.message).toContain("Anna's Archive");
    expect(ebookScraperMock.searchByAsin).toHaveBeenCalledWith(
      'B001ASIN',
      'epub',
      'https://annas-archive.gl',
      expect.anything(),
      undefined,
      'en'
    );
    expect(jobQueueMock.addStartDirectDownloadJob).toHaveBeenCalledWith(
      'req-1',
      'dh-1',
      'https://slow1.example.com/abc123',
      'Test Book - Test Author.epub',
      undefined
    );
  });

  it('falls back to title search when ASIN search fails', async () => {
    prismaMock.request.update.mockResolvedValue({});
    prismaMock.downloadHistory.create.mockResolvedValue({ id: 'dh-2' });
    prismaMock.downloadHistory.update.mockResolvedValue({});

    ebookScraperMock.searchByAsin.mockResolvedValue(null);
    ebookScraperMock.searchByTitle.mockResolvedValue('xyz789md5');
    ebookScraperMock.getSlowDownloadLinks.mockResolvedValue([
      'https://slow1.example.com/xyz789',
    ]);

    const { processSearchEbook } = await import('@/lib/processors/search-ebook.processor');

    const result = await processSearchEbook({
      requestId: 'req-2',
      audiobook: {
        id: 'ab-2',
        title: 'Another Book',
        author: 'Another Author',
        asin: 'B002ASIN',
      },
      jobId: 'job-2',
    });

    expect(result.success).toBe(true);
    expect(result.message).toContain("Anna's Archive");
    expect(ebookScraperMock.searchByAsin).toHaveBeenCalled();
    expect(ebookScraperMock.searchByTitle).toHaveBeenCalledWith(
      'Another Book',
      'Another Author',
      'epub',
      'https://annas-archive.gl',
      expect.anything(),
      undefined,
      'en'
    );
  });

  it('searches by title when no ASIN is available', async () => {
    prismaMock.request.update.mockResolvedValue({});
    prismaMock.downloadHistory.create.mockResolvedValue({ id: 'dh-3' });
    prismaMock.downloadHistory.update.mockResolvedValue({});

    ebookScraperMock.searchByTitle.mockResolvedValue('noasin123');
    ebookScraperMock.getSlowDownloadLinks.mockResolvedValue([
      'https://slow.example.com/noasin123',
    ]);

    const { processSearchEbook } = await import('@/lib/processors/search-ebook.processor');

    const result = await processSearchEbook({
      requestId: 'req-3',
      audiobook: {
        id: 'ab-3',
        title: 'No ASIN Book',
        author: 'No ASIN Author',
        // No asin field
      },
      jobId: 'job-3',
    });

    expect(result.success).toBe(true);
    expect(ebookScraperMock.searchByAsin).not.toHaveBeenCalled();
    expect(ebookScraperMock.searchByTitle).toHaveBeenCalled();
  });

  it('marks request as awaiting_search when no ebook found', async () => {
    prismaMock.request.update.mockResolvedValue({});

    ebookScraperMock.searchByAsin.mockResolvedValue(null);
    ebookScraperMock.searchByTitle.mockResolvedValue(null);

    const { processSearchEbook } = await import('@/lib/processors/search-ebook.processor');

    const result = await processSearchEbook({
      requestId: 'req-4',
      audiobook: {
        id: 'ab-4',
        title: 'Unfindable Book',
        author: 'Unknown Author',
        asin: 'B004ASIN',
      },
      jobId: 'job-4',
    });

    expect(result.success).toBe(false);
    expect(result.message).toContain('re-search');
    expect(prismaMock.request.update).toHaveBeenCalledWith({
      where: { id: 'req-4' },
      data: expect.objectContaining({
        status: 'awaiting_search',
        errorMessage: expect.stringContaining('No ebook found'),
        lastSearchAt: expect.any(Date),
      }),
    });
    expect(jobQueueMock.addStartDirectDownloadJob).not.toHaveBeenCalled();
  });

  it('marks request as awaiting_search when no download links available', async () => {
    prismaMock.request.update.mockResolvedValue({});

    ebookScraperMock.searchByAsin.mockResolvedValue('md5nolinks');
    ebookScraperMock.getSlowDownloadLinks.mockResolvedValue([]);

    const { processSearchEbook } = await import('@/lib/processors/search-ebook.processor');

    const result = await processSearchEbook({
      requestId: 'req-5',
      audiobook: {
        id: 'ab-5',
        title: 'Book No Links',
        author: 'Author No Links',
        asin: 'B005ASIN',
      },
      jobId: 'job-5',
    });

    expect(result.success).toBe(false);
    expect(result.message).toContain('re-search');
    expect(prismaMock.request.update).toHaveBeenCalledWith({
      where: { id: 'req-5' },
      data: expect.objectContaining({
        status: 'awaiting_search',
        errorMessage: expect.stringContaining('No ebook found'),
        lastSearchAt: expect.any(Date),
      }),
    });
  });

  it('uses FlareSolverr when configured', async () => {
    prismaMock.request.update.mockResolvedValue({});
    prismaMock.downloadHistory.create.mockResolvedValue({ id: 'dh-6' });
    prismaMock.downloadHistory.update.mockResolvedValue({});

    configServiceMock.get.mockImplementation(async (key: string) => {
      if (key === 'ebook_sidecar_preferred_format') return 'epub';
      if (key === 'ebook_sidecar_base_url') return 'https://annas-archive.gl';
      if (key === 'ebook_sidecar_flaresolverr_url') return 'http://flaresolverr:8191';
      if (key === 'ebook_annas_archive_enabled') return 'true';
      if (key === 'ebook_indexer_search_enabled') return 'false';
      return null;
    });

    ebookScraperMock.searchByAsin.mockResolvedValue('md5withflare');
    ebookScraperMock.getSlowDownloadLinks.mockResolvedValue(['https://slow.example.com/flare']);

    const { processSearchEbook } = await import('@/lib/processors/search-ebook.processor');

    await processSearchEbook({
      requestId: 'req-6',
      audiobook: {
        id: 'ab-6',
        title: 'Flare Book',
        author: 'Flare Author',
        asin: 'B006ASIN',
      },
      jobId: 'job-6',
    });

    expect(ebookScraperMock.searchByAsin).toHaveBeenCalledWith(
      'B006ASIN',
      'epub',
      'https://annas-archive.gl',
      expect.anything(),
      'http://flaresolverr:8191',
      'en'
    );
  });

  it('fails request on unexpected errors', async () => {
    prismaMock.request.update.mockResolvedValue({});

    ebookScraperMock.searchByAsin.mockRejectedValue(new Error('Network error'));

    const { processSearchEbook } = await import('@/lib/processors/search-ebook.processor');

    await expect(processSearchEbook({
      requestId: 'req-7',
      audiobook: {
        id: 'ab-7',
        title: 'Error Book',
        author: 'Error Author',
        asin: 'B007ASIN',
      },
      jobId: 'job-7',
    })).rejects.toThrow('Network error');

    expect(prismaMock.request.update).toHaveBeenCalledWith({
      where: { id: 'req-7' },
      data: expect.objectContaining({
        status: 'failed',
        errorMessage: 'Network error',
      }),
    });
  });

  it('creates download history with correct metadata', async () => {
    prismaMock.request.update.mockResolvedValue({});
    prismaMock.downloadHistory.create.mockResolvedValue({ id: 'dh-8' });
    prismaMock.downloadHistory.update.mockResolvedValue({});

    ebookScraperMock.searchByAsin.mockResolvedValue('md5metadata');
    ebookScraperMock.getSlowDownloadLinks.mockResolvedValue([
      'https://link1.example.com',
      'https://link2.example.com',
    ]);

    const { processSearchEbook } = await import('@/lib/processors/search-ebook.processor');

    await processSearchEbook({
      requestId: 'req-8',
      audiobook: {
        id: 'ab-8',
        title: 'Metadata Book',
        author: 'Metadata Author',
        asin: 'B008ASIN',
      },
      jobId: 'job-8',
    });

    expect(prismaMock.downloadHistory.create).toHaveBeenCalledWith({
      data: expect.objectContaining({
        requestId: 'req-8',
        indexerName: "Anna's Archive",
        torrentName: 'Metadata Book - Metadata Author.epub',
        downloadClient: 'direct',
        downloadStatus: 'queued',
        selected: true,
        qualityScore: 100, // ASIN match = 100
      }),
    });

    // Check that all URLs are stored
    expect(prismaMock.downloadHistory.update).toHaveBeenCalledWith({
      where: { id: 'dh-8' },
      data: {
        torrentUrl: JSON.stringify([
          'https://link1.example.com',
          'https://link2.example.com',
        ]),
      },
    });
  });
});
