import { generateKeyPairSync } from 'node:crypto';

import { expect, test, type Page, type Route } from '@playwright/test';

const response = (data: unknown) => ({ result: { data } });
const operationNames = (route: Route): string[] => {
    const url = new URL(route.request().url());
    return decodeURIComponent(url.pathname.slice(url.pathname.lastIndexOf('/trpc/') + 6)).split(',');
};

const { publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
const publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' }).toString();

const installPasswordSetupFixture = async (page: Page) => {
    const calls: string[] = [];
    await page.route('**/gateway/api/trpc/**', async (route) => {
        const results = operationNames(route).map((operation) => {
            calls.push(operation);
            if (operation === 'me') return response(null);
            if (operation === 'lobby.notice') return response('');
            if (operation === 'lobby.profiles') return response([]);
            if (operation === 'auth.passwordKey') {
                return response({ keyId: 'password-setup-key', publicKeyPem, algorithm: 'RSA-OAEP-256' });
            }
            if (operation === 'auth.kakaoExchange') {
                return response({
                    status: 'password_setup',
                    oauthSessionId: '11111111-1111-4111-8111-111111111112',
                    email: 'migrated@example.test',
                    successStatus: 'login',
                });
            }
            if (operation === 'auth.kakaoSetPassword') {
                return response({
                    status: 'otp',
                    challengeId: '11111111-1111-4111-8111-111111111111',
                    expiresAt: '2026-08-17T12:03:00.000Z',
                    attemptsRemaining: 3,
                });
            }
            throw new Error(`Unhandled password setup fixture operation: ${operation}`);
        });
        await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(results) });
    });
    return calls;
};

const installFixture = async (page: Page, action: 'link_existing' | 'rejoin') => {
    const calls: string[] = [];
    await page.route('**/gateway/api/trpc/**', async (route) => {
        const operations = operationNames(route);
        const results = operations.map((operation) => {
            calls.push(operation);
            if (operation === 'me') return response(null);
            if (operation === 'lobby.notice') return response('');
            if (operation === 'lobby.profiles') return response([]);
            if (operation === 'auth.kakaoExchange') {
                return response({
                    status: 'account_recovery',
                    action,
                    oauthSessionId: `${action}-session`,
                    email: 'retained@example.test',
                });
            }
            if (operation === 'auth.kakaoResolveAccount') {
                return action === 'link_existing'
                    ? response({
                          status: 'otp',
                          successStatus: 'login',
                          challengeId: '11111111-1111-4111-8111-111111111111',
                          expiresAt: '2026-08-08T12:03:00.000Z',
                          attemptsRemaining: 3,
                      })
                    : response({
                          status: 'join',
                          oauthSessionId: 'confirmed-registration-session',
                          email: 'retained@example.test',
                      });
            }
            throw new Error(`Unhandled Kakao account recovery fixture operation: ${operation}`);
        });
        await route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify(results),
        });
    });
    return calls;
};

const verifyRecoveryChoice = async (page: Page, action: 'link_existing' | 'rejoin') => {
    const group = page.getByRole('group', { name: '카카오 계정 연결 확인' });
    const confirm = group.getByRole('button', {
        name: action === 'link_existing' ? '기존 계정에 연결' : '재가입',
    });
    await expect(group).toBeVisible();
    await expect(group).toContainText('retained@example.test');
    await expect(group).toContainText(
        action === 'link_existing' ? '이 계정에 카카오 로그인을 연결해드릴까요?' : '새 계정으로 재가입하시겠습니까?'
    );
    const geometry = await group.evaluate((element) => {
        const rect = element.getBoundingClientRect();
        const style = getComputedStyle(element);
        return {
            width: rect.width,
            height: rect.height,
            backgroundColor: style.backgroundColor,
            fontSize: style.fontSize,
        };
    });
    expect(geometry.width).toBeGreaterThan(300);
    expect(geometry.backgroundColor).toBe('rgba(0, 0, 0, 0)');

    await confirm.hover();
    await page.waitForTimeout(200);
    expect(await confirm.evaluate((element) => getComputedStyle(element).backgroundColor)).toBe('rgb(47, 77, 108)');
    await confirm.focus();
    expect(await confirm.evaluate((element) => getComputedStyle(element).outlineStyle)).toBe('solid');
    return { group, confirm, geometry };
};

for (const viewport of [
    { name: 'desktop', width: 1200, height: 900 },
    { name: 'mobile', width: 390, height: 844 },
] as const) {
    test(`links the retained email account only after confirmation on ${viewport.name}`, async ({ page }) => {
        const calls = await installFixture(page, 'link_existing');
        await page.setViewportSize(viewport);
        await page.goto('/gateway/oauth/callback?code=oauth-code&state=oauth-state');
        const { confirm, geometry } = await verifyRecoveryChoice(page, 'link_existing');

        await confirm.click();
        await expect(page.getByRole('dialog', { name: '인증 코드 필요' })).toBeVisible();
        expect(calls.filter((operation) => operation === 'auth.kakaoResolveAccount')).toHaveLength(1);
        expect(geometry.width).toBe(viewport.name === 'desktop' ? 698 : 372);
    });

    test(`continues an orphaned Kakao connection as a new registration on ${viewport.name}`, async ({ page }) => {
        const calls = await installFixture(page, 'rejoin');
        await page.setViewportSize(viewport);
        await page.goto('/gateway/oauth/callback?code=oauth-code&state=oauth-state');
        const { confirm, geometry } = await verifyRecoveryChoice(page, 'rejoin');

        await confirm.click();
        await expect(page.getByRole('heading', { name: '회원가입' })).toBeVisible();
        await expect(page.getByLabel('카카오 이메일')).toHaveValue('retained@example.test');
        expect(calls.filter((operation) => operation === 'auth.kakaoResolveAccount')).toHaveLength(1);
        expect(geometry.width).toBe(viewport.name === 'desktop' ? 698 : 372);
    });

    test(`sets a migrated password before opening the OTP dialog on ${viewport.name}`, async ({ page }) => {
        const calls = await installPasswordSetupFixture(page);
        await page.setViewportSize(viewport);
        await page.goto('/gateway/oauth/callback?code=oauth-code&state=oauth-state');

        const form = page.getByRole('form', { name: '새 비밀번호 설정' });
        await expect(form).toBeVisible();
        await expect(form).toContainText('카카오 인증으로 기존 계정을 확인했습니다.');
        await expect(form.getByLabel('카카오 이메일')).toHaveValue('migrated@example.test');
        const geometry = await form.evaluate((element) => {
            const rect = element.getBoundingClientRect();
            return { width: rect.width, right: rect.right };
        });
        await form.getByLabel('새 비밀번호').fill('new-password-value');
        await form.getByLabel('비밀번호 확인').fill('new-password-value');
        await form.getByRole('button', { name: '새 비밀번호 설정' }).click();

        await expect(page.getByRole('dialog', { name: '인증 코드 필요' })).toBeVisible();
        expect(calls.filter((operation) => operation === 'auth.kakaoSetPassword')).toHaveLength(1);
        expect(geometry.width).toBeGreaterThan(300);
        expect(geometry.right).toBeLessThanOrEqual(viewport.width);
    });
}
