import { test, expect, type Page } from './fixtures';

// Phase 3: the manga detail page gains a mobile hero (blurred backdrop
// + transparent app bar), a sticky bottom CTA whose wording reflects
// read progress, a 3-line description clamp with Read more, and an
// overflow Sheet that hosts secondary actions (Edit / Upload chapter /
// Add to collection / Force resync). Desktop layout is preserved.

const MOBILE = { width: 390, height: 844 } as const;
const DESKTOP = { width: 1280, height: 720 } as const;

const mangaId = 'a1111111-1111-1111-1111-111111111111';
const firstChapterId = 'c1111111-1111-1111-1111-111111111111';
const latestChapterId = 'c2222222-2222-2222-2222-222222222222';

// Chapters arrive newest-first from the API (source_index DESC) — the
// detail page's "Read first chapter" CTA targets the last element.
const chaptersFixture = [
    {
        id: latestChapterId,
        manga_id: mangaId,
        number: 2,
        title: 'Second',
        page_count: 10,
        created_at: '2026-02-01T00:00:00Z'
    },
    {
        id: firstChapterId,
        manga_id: mangaId,
        number: 1,
        title: 'The Brand',
        page_count: 8,
        created_at: '2026-01-01T00:00:00Z'
    }
];

const shortDescription = 'A brief synopsis.';
const longDescription =
    'This is an extended description that runs over multiple lines so that ' +
    'the clamp + Read more behavior on mobile has actual content to truncate. ' +
    'Long-form descriptions are common for manga that have been crawled from ' +
    'public sources with editorial blurbs, so the catalog has to handle them ' +
    'gracefully without pushing the chapter list below the fold on phones.';

function mangaFixture(description: string = shortDescription) {
    return {
        id: mangaId,
        title: 'Berserk',
        status: 'ongoing',
        alt_titles: [],
        description,
        cover_image_path: `mangas/${mangaId}/cover.png`,
        created_at: '2026-01-01T00:00:00Z',
        updated_at: '2026-01-01T00:00:00Z',
        authors: [{ id: 'au1', name: 'Kentaro Miura' }],
        genres: [],
        tags: []
    };
}

async function mockDetail(
    page: Page,
    opts: {
        description?: string;
        readProgress?: {
            chapter_id: string;
            chapter_number: number;
            page: number;
        } | null;
        authed?: boolean;
        similar?: Array<Record<string, unknown>>;
    } = {}
) {
    const authed = opts.authed ?? false;

    await page.route('**/api/v1/auth/config', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({ self_register_enabled: true, private_mode: false })
        })
    );
    await page.route('**/api/v1/auth/me', (route) =>
        route.fulfill({
            status: authed ? 200 : 401,
            contentType: 'application/json',
            body: authed
                ? JSON.stringify({
                      user: {
                          id: 'u1',
                          username: 'reader',
                          created_at: '2026-01-01T00:00:00Z',
                          is_admin: false
                      }
                  })
                : JSON.stringify({
                      error: { code: 'unauthenticated', message: 'unauthenticated' }
                  })
        })
    );
    await page.route('**/api/v1/auth/me/preferences', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({})
        })
    );
    await page.route('**/api/v1/me/bookmarks*', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({ items: [], page: { limit: 50, offset: 0, total: 0 } })
        })
    );
    await page.route(`**/api/v1/mangas/${mangaId}`, (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify(mangaFixture(opts.description))
        })
    );
    await page.route(`**/api/v1/mangas/${mangaId}/chapters*`, (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({
                items: chaptersFixture,
                page: { limit: 50, offset: 0, total: chaptersFixture.length }
            })
        })
    );
    await page.route(`**/api/v1/mangas/${mangaId}/similar`, (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({ items: opts.similar ?? [] })
        })
    );
    await page.route(`**/api/v1/me/read-progress/${mangaId}`, (route) =>
        route.fulfill({
            status: opts.readProgress ? 200 : 404,
            contentType: 'application/json',
            body: opts.readProgress
                ? JSON.stringify(opts.readProgress)
                : JSON.stringify({ error: { code: 'not_found', message: 'not found' } })
        })
    );
    // 1x1 transparent PNG so cover image requests don't 404 noisily.
    const png = Buffer.from(
        '89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4890000000d49444154789c63000100000005000158a3b62a0000000049454e44ae426082',
        'hex'
    );
    await page.route('**/api/v1/files/**', (route) =>
        route.fulfill({ status: 200, contentType: 'image/png', body: png })
    );
}

test.describe('mobile manga detail', () => {
    test('phone viewport: with no progress, CTA reads "Read first chapter" and links to the oldest chapter', async ({
        page
    }) => {
        await mockDetail(page);
        await page.setViewportSize(MOBILE);
        await page.goto(`/manga/${mangaId}`);

        await expect(page.getByTestId('mobile-hero')).toBeVisible();

        const cta = page.getByTestId('continue-cta');
        await expect(cta).toHaveText('Read first chapter');
        await expect(cta).toHaveAttribute(
            'href',
            `/manga/${mangaId}/chapter/${firstChapterId}`
        );
    });

    test('phone viewport: with progress, CTA reads "Continue {chapterLabel}" and links to the in-progress chapter', async ({
        page
    }) => {
        await mockDetail(page, {
            authed: true,
            readProgress: { chapter_id: latestChapterId, chapter_number: 2, page: 5 }
        });
        await page.setViewportSize(MOBILE);
        await page.goto(`/manga/${mangaId}`);

        const cta = page.getByTestId('continue-cta');
        // chapterLabel returns the title when present, falling back to
        // "Chapter N" only when empty — the fixture has title "Second".
        await expect(cta).toHaveText(/Continue\s+Second/);
        await expect(cta).toHaveAttribute(
            'href',
            `/manga/${mangaId}/chapter/${latestChapterId}`
        );
    });

    test('phone viewport: short description shows no Read more toggle', async ({ page }) => {
        await mockDetail(page, { description: shortDescription });
        await page.setViewportSize(MOBILE);
        await page.goto(`/manga/${mangaId}`);

        await expect(page.getByTestId('manga-description')).toBeVisible();
        await expect(page.getByTestId('read-more-toggle')).toHaveCount(0);
    });

    test('phone viewport: long description shows Read more, expands on tap, collapses on second tap', async ({
        page
    }) => {
        await mockDetail(page, { description: longDescription });
        await page.setViewportSize(MOBILE);
        await page.goto(`/manga/${mangaId}`);

        const toggle = page.getByTestId('read-more-toggle');
        await expect(toggle).toHaveText('Read more');

        await toggle.click();
        await expect(toggle).toHaveText('Read less');

        await toggle.click();
        await expect(toggle).toHaveText('Read more');
    });

    test('phone viewport: overflow ⋯ opens the actions sheet with Edit / Upload / Add-to-collection rows', async ({
        page
    }) => {
        await mockDetail(page, { authed: true });
        await page.setViewportSize(MOBILE);
        await page.goto(`/manga/${mangaId}`);

        await expect(page.getByTestId('detail-overflow-sheet')).toBeHidden();
        await page.getByTestId('detail-overflow').click();

        const sheet = page.getByTestId('detail-overflow-sheet');
        await expect(sheet).toBeVisible();
        await expect(sheet.getByTestId('overflow-edit')).toBeVisible();
        await expect(sheet.getByTestId('overflow-upload-chapter')).toBeVisible();
        await expect(sheet.getByTestId('overflow-add-to-collection')).toBeVisible();
    });

    test('phone viewport: force resync from the overflow sheet surfaces a result message', async ({
        page
    }) => {
        await mockDetail(page, { authed: true });
        // Upgrade the session to admin so the Force resync action is offered.
        // Registered after mockDetail so this auth/me wins (Playwright routes
        // resolve most-recently-registered first).
        await page.route('**/api/v1/auth/me', (route) =>
            route.fulfill({
                status: 200,
                contentType: 'application/json',
                body: JSON.stringify({
                    user: {
                        id: 'u1',
                        username: 'admin',
                        created_at: '2026-01-01T00:00:00Z',
                        is_admin: true
                    }
                })
            })
        );
        await page.route(`**/api/v1/admin/mangas/${mangaId}/resync`, (route) =>
            route.fulfill({
                status: 200,
                contentType: 'application/json',
                body: JSON.stringify({
                    manga: mangaFixture(),
                    cover_fetched: true,
                    metadata_status: 'updated'
                })
            })
        );
        await page.setViewportSize(MOBILE);
        await page.goto(`/manga/${mangaId}`);

        await page.getByTestId('detail-overflow').click();
        await page.getByTestId('overflow-force-resync').click();

        // The result was previously display:none under 640px, leaving mobile
        // admins with no feedback. It must now be visible.
        const msg = page.getByTestId('force-resync-message');
        await expect(msg).toBeVisible();
        await expect(msg).toContainText(/Metadata updated/i);
    });

    test('desktop viewport: mobile hero is hidden, existing layout + action-row stays', async ({
        page
    }) => {
        await mockDetail(page, { authed: true });
        await page.setViewportSize(DESKTOP);
        await page.goto(`/manga/${mangaId}`);

        // Hero block exists in DOM but is hidden by CSS at >640px.
        await expect(page.getByTestId('mobile-hero')).toBeHidden();
        // The desktop title/cover/action surfaces remain visible.
        await expect(page.getByTestId('manga-title')).toBeVisible();
        await expect(page.getByTestId('bookmark-toggle')).toBeVisible();
    });

    test('renders the Similar section with recommended cards when present', async ({
        page
    }) => {
        await mockDetail(page, {
            similar: [
                {
                    id: 'b2222222-2222-2222-2222-222222222222',
                    title: 'Vinland Saga',
                    status: 'ongoing',
                    alt_titles: [],
                    description: null,
                    cover_image_path: null,
                    created_at: '2026-01-01T00:00:00Z',
                    updated_at: '2026-01-01T00:00:00Z',
                    authors: [{ id: 'au2', name: 'Makoto Yukimura' }],
                    genres: []
                }
            ]
        });
        await page.goto(`/manga/${mangaId}`);

        const section = page.getByTestId('similar-section');
        await expect(section).toBeVisible();
        await expect(section.getByText('Vinland Saga')).toBeVisible();
    });

    test('omits the Similar section when there are no recommendations', async ({ page }) => {
        await mockDetail(page, { similar: [] });
        await page.goto(`/manga/${mangaId}`);

        // The chapter list is the anchor that the page has rendered.
        await expect(page.getByTestId('manga-title')).toBeVisible();
        await expect(page.getByTestId('similar-section')).toHaveCount(0);
    });
});
