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

const userFixture = {
    id: 'u1',
    username: 'alice',
    created_at: '2026-01-01T00:00:00Z'
};
const mangaFixture = {
    id: 'm1',
    title: 'Berserk',
    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: 'a1', name: 'Kentaro Miura' }],
    genres: [],
    tags: []
};

async function stubAuthenticatedAndGenres(page: Page) {
    await page.route('**/api/v1/auth/me', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({ user: userFixture })
        })
    );
    await page.route('**/api/v1/genres', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify([
                { id: 'g-action', name: 'Action' },
                { id: 'g-fantasy', name: 'Fantasy' }
            ])
        })
    );
}

test('anonymous user sees sign-in prompt on /upload', async ({ page }) => {
    await page.route('**/api/v1/auth/me', (route) =>
        route.fulfill({
            status: 401,
            contentType: 'application/json',
            body: JSON.stringify({
                error: { code: 'unauthenticated', message: 'unauthenticated' }
            })
        })
    );
    await page.route('**/api/v1/genres', (route) =>
        route.fulfill({ status: 200, contentType: 'application/json', body: '[]' })
    );

    await page.goto('/upload');
    await expect(page.getByTestId('upload-signin')).toBeVisible();
});

test('/upload creates a manga with no staged chapters and lands on the manga page', async ({
    page
}) => {
    await stubAuthenticatedAndGenres(page);

    let createdManga: typeof mangaFixture | null = null;
    await page.route('**/api/v1/mangas', (route) => {
        if (route.request().method() === 'POST') {
            createdManga = { ...mangaFixture, id: 'm2', title: 'Naruto' };
            route.fulfill({
                status: 201,
                contentType: 'application/json',
                body: JSON.stringify(createdManga)
            });
        } else {
            route.fallback();
        }
    });
    await page.route('**/api/v1/mangas/m2', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({ ...mangaFixture, id: 'm2', title: 'Naruto' })
        })
    );
    await page.route('**/api/v1/mangas/m2/chapters*', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({
                items: [],
                page: { limit: 50, offset: 0, total: 0 }
            })
        })
    );
    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/me/read-progress/m2', (route) =>
        route.fulfill({
            status: 404,
            contentType: 'application/json',
            body: JSON.stringify({
                error: { code: 'not_found', message: 'no progress' }
            })
        })
    );

    await page.goto('/upload');
    await page.getByTestId('manga-title').fill('Naruto');
    await page.getByTestId('manga-submit').click();
    // After create, success → navigate to /manga/{id}.
    await expect(page).toHaveURL(/\/manga\/m2$/);
    expect(createdManga).not.toBeNull();
});

test('/upload stages a chapter with renamed page files (page-NNN.<ext>)', async ({
    page
}) => {
    await stubAuthenticatedAndGenres(page);

    let createdManga: typeof mangaFixture | null = null;
    let submittedPageNames: string[] = [];

    await page.route('**/api/v1/mangas', (route) => {
        if (route.request().method() === 'POST') {
            createdManga = { ...mangaFixture, id: 'm3', title: 'Vinland Saga' };
            route.fulfill({
                status: 201,
                contentType: 'application/json',
                body: JSON.stringify(createdManga)
            });
        } else {
            route.fallback();
        }
    });
    await page.route('**/api/v1/mangas/m3/chapters', (route) => {
        if (route.request().method() === 'POST') {
            const post = route.request().postDataBuffer()?.toString('binary') ?? '';
            // Pull every Content-Disposition filename out of the
            // multipart body — that's what the server (and proxies,
            // logs) would see. We expect only renamed `page-NNN.*`
            // entries, never the original filenames.
            const matches = [
                ...post.matchAll(/filename="([^"]+)"/g)
            ].map((m) => m[1]);
            submittedPageNames = matches.filter((n) => n.startsWith('page-'));
            route.fulfill({
                status: 201,
                contentType: 'application/json',
                body: JSON.stringify({
                    id: 'c1',
                    manga_id: 'm3',
                    number: 1,
                    title: null,
                    page_count: 2,
                    created_at: '2026-01-01T00:00:00Z'
                })
            });
        } else {
            route.fallback();
        }
    });
    await page.route('**/api/v1/mangas/m3', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({ ...mangaFixture, id: 'm3', title: 'Vinland Saga' })
        })
    );
    await page.route('**/api/v1/mangas/m3/chapters?*', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({
                items: [],
                page: { limit: 50, offset: 0, total: 0 }
            })
        })
    );
    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/me/read-progress/m3', (route) =>
        route.fulfill({
            status: 404,
            contentType: 'application/json',
            body: JSON.stringify({
                error: { code: 'not_found', message: 'no progress' }
            })
        })
    );

    await page.goto('/upload');
    await page.getByTestId('manga-title').fill('Vinland Saga');
    await page.getByTestId('add-chapter').click();
    const pngBytes = Buffer.from([
        0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a
    ]);
    await page
        .getByTestId('staged-chapter-pages-input')
        .setInputFiles([
            { name: 'IMG_2837.png', mimeType: 'image/png', buffer: pngBytes },
            { name: 'random_file.png', mimeType: 'image/png', buffer: pngBytes }
        ]);
    // The list renders "Page 001" / "Page 002" not the original filenames.
    const list = page.getByTestId('staged-chapter-pages-list');
    await expect(list).toContainText('Page 001');
    await expect(list).toContainText('Page 002');
    // Original filenames are visible as a dimmed caption (uploader-
    // reference; dropped after the row).
    await expect(list).toContainText('IMG_2837.png');

    await page.getByTestId('manga-submit').click();
    await expect(page).toHaveURL(/\/manga\/m3$/);
    expect(submittedPageNames).toEqual(['page-001.png', 'page-002.png']);
});

test('/manga/[id]/upload-chapter happy path uploads renamed pages', async ({
    page
}) => {
    await stubAuthenticatedAndGenres(page);

    await page.route('**/api/v1/mangas/m1', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify(mangaFixture)
        })
    );
    await page.route('**/api/v1/mangas/m1/chapters?*', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({
                items: [{ id: 'c0', manga_id: 'm1', number: 1, title: null, page_count: 3, created_at: '2026-01-01T00:00:00Z' }],
                page: { limit: 200, offset: 0, total: 1 }
            })
        })
    );
    let submitted: string[] = [];
    await page.route('**/api/v1/mangas/m1/chapters', (route) => {
        if (route.request().method() === 'POST') {
            const post = route.request().postDataBuffer()?.toString('binary') ?? '';
            submitted = [...post.matchAll(/filename="([^"]+)"/g)].map((m) => m[1]);
            route.fulfill({
                status: 201,
                contentType: 'application/json',
                body: JSON.stringify({
                    id: 'c-new',
                    manga_id: 'm1',
                    number: 2,
                    title: null,
                    page_count: 1,
                    created_at: '2026-01-01T00:00:00Z'
                })
            });
        } else {
            route.fallback();
        }
    });
    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/me/read-progress/m1', (route) =>
        route.fulfill({
            status: 404,
            contentType: 'application/json',
            body: JSON.stringify({
                error: { code: 'not_found', message: 'no progress' }
            })
        })
    );

    await page.goto('/manga/m1/upload-chapter');
    // Default chapter number is the next free one (existing max 1 → 2).
    await expect(page.getByTestId('chapter-number')).toHaveValue('2');

    const pngBytes = Buffer.from([
        0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a
    ]);
    await page.getByTestId('pages-input').setInputFiles({
        name: 'whatever.png',
        mimeType: 'image/png',
        buffer: pngBytes
    });
    await expect(page.getByTestId('pages-list')).toContainText('Page 001');

    await page.getByTestId('chapter-submit').click();
    await expect(page).toHaveURL(/\/manga\/m1$/);
    expect(submitted.filter((n) => n.startsWith('page-'))).toEqual([
        'page-001.png'
    ]);
});

test('chapter upload client preflight blocks oversized files', async ({
    page
}) => {
    await stubAuthenticatedAndGenres(page);
    await page.route('**/api/v1/mangas/m1', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify(mangaFixture)
        })
    );
    await page.route('**/api/v1/mangas/m1/chapters?*', (route) =>
        route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({
                items: [],
                page: { limit: 200, offset: 0, total: 0 }
            })
        })
    );
    let chapterPostCalls = 0;
    await page.route('**/api/v1/mangas/m1/chapters', (route) => {
        if (route.request().method() === 'POST') chapterPostCalls += 1;
        route.fallback();
    });

    await page.goto('/manga/m1/upload-chapter');
    const big = Buffer.alloc(21 * 1024 * 1024, 0xff);
    await page.getByTestId('pages-input').setInputFiles({
        name: 'huge.png',
        mimeType: 'image/png',
        buffer: big
    });

    await expect(page.getByTestId('pages-list')).toContainText('too large');
    await expect(page.getByTestId('chapter-submit')).toBeDisabled();
    expect(chapterPostCalls).toBe(0);
});
