/**
 * Mobile compose/.env editing (below the md breakpoint).
 *
 * Logs in and creates the stack at desktop width (the create dialog and stack
 * list are desktop-driven), then resizes to a phone viewport. Empty-stack
 * create opens compose edit immediately (startInComposeEdit); on phone that
 * surfaces as MobileComposeEditor, so these tests wait for that editor rather
 * than assuming an Anatomy/Compose read-only gate first.
 * Phone widths exercised: 390px and 430px. The compose/.env toggle and env-file
 * save share the same handlers and are covered by MobileStackDetail.test.tsx.
 */
import { test, expect, type Page } from '@playwright/test';
import { loginAs, waitForStacksLoaded } from './helpers';

const TEST_STACK = 'e2e-mobile-edit-stack';
const PHONE_390 = { width: 390, height: 844 };
const PHONE_430 = { width: 430, height: 932 };

async function deleteTestStack(page: Page) {
    await page.evaluate(async (name) => {
        await fetch(`/api/stacks/${name}`, { method: 'DELETE', credentials: 'include' }).catch(() => { });
    }, TEST_STACK);
}

async function createTestStack(page: Page) {
    await page.getByRole('button', { name: 'Create Stack' }).click();
    await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5_000 });
    await page.locator('#create-stack-name').fill(TEST_STACK);
    await page.locator('[role="dialog"]').getByRole('button', { name: 'Create' }).click();
    await expect(page.getByRole('dialog')).toBeHidden({ timeout: 8_000 });
    // Empty create auto-opens desktop compose edit before we shrink the viewport.
    await expect(page.getByRole('button', { name: 'Save & Deploy', exact: true })).toBeVisible({ timeout: 10_000 });
}

/** Shrink to phone and wait for the already-open compose editor surface. */
async function openComposeEditorOnPhone(page: Page, viewport = PHONE_390) {
    await page.setViewportSize(viewport);
    await expect(page.getByTestId('mobile-compose-editor')).toBeVisible({ timeout: 5_000 });
}

test.describe('mobile stack editing', () => {
    test.beforeEach(async ({ page }) => {
        await loginAs(page);
        await waitForStacksLoaded(page);
        await deleteTestStack(page);
        await page.reload();
        await loginAs(page);
        await waitForStacksLoaded(page);
        await createTestStack(page);
    });

    test.afterEach(async ({ page }) => {
        await page.setViewportSize({ width: 1280, height: 800 });
        await deleteTestStack(page);
    });

    test('edits and saves the compose file from a phone (390px)', async ({ page }) => {
        await openComposeEditorOnPhone(page);

        await page.getByTestId('mobile-compose-editor').fill('services:\n  app:\n    image: nginx:1.27\n    restart: always\n');
        await page.getByTestId('mobile-editor-save').click();

        await expect(page.getByText(/file saved successfully/i)).toBeVisible({ timeout: 5_000 });

        // The edit must actually reach disk, not just flash a toast.
        const onDisk = await page.evaluate(async (name) => {
            const res = await fetch(`/api/stacks/${name}`, { credentials: 'include' });
            return res.text();
        }, TEST_STACK);
        expect(onDisk).toContain('nginx:1.27');
    });

    test('does not deploy when the save PUT fails', async ({ page }) => {
        await page.route(`**/api/stacks/${TEST_STACK}`, async (route, req) => {
            if (req.method() === 'PUT') {
                await route.fulfill({ status: 500, body: 'forced save failure' });
                return;
            }
            await route.continue();
        });
        let deployAttempts = 0;
        await page.route(`**/api/stacks/${TEST_STACK}/deploy*`, async (route, req) => {
            if (req.method() === 'POST') deployAttempts += 1;
            await route.continue();
        });

        await openComposeEditorOnPhone(page);
        await page.getByTestId('mobile-editor-save-deploy').click();

        await expect(page.getByText(/failed to save file/i)).toBeVisible({ timeout: 5_000 });
        await page.waitForTimeout(1_000);
        expect(deployAttempts).toBe(0);
    });

    test('guards a dirty close and discards on confirm', async ({ page }) => {
        await openComposeEditorOnPhone(page);

        await page.getByTestId('mobile-compose-editor').fill('services:\n  app:\n    image: nginx:1.28\n');
        await page.getByTestId('mobile-editor-close').click();

        const dialog = page.getByRole('alertdialog', { name: /discard unsaved changes/i });
        await expect(dialog).toBeVisible({ timeout: 5_000 });
        await page.getByRole('button', { name: 'Discard changes' }).click();

        // The editor closes back to mobile stack detail (default Logs segment).
        await expect(page.getByTestId('mobile-compose-editor')).toBeHidden();
        await page.getByRole('tab', { name: 'Compose' }).click();
        await expect(page.getByRole('button', { name: 'edit' })).toBeVisible();
    });

    test('opens a usable editor at 430px', async ({ page }) => {
        await openComposeEditorOnPhone(page, PHONE_430);

        await expect(page.getByTestId('mobile-compose-editor')).toBeVisible();
        await expect(page.getByTestId('mobile-editor-save')).toBeVisible();
        await expect(page.getByTestId('mobile-editor-save-deploy')).toBeVisible();
    });
});
