import { expect } from '@playwright/test';
import type { Browser, Page } from '@playwright/test';
import { PLAYWRIGHT_BASE_URL } from '../../playwright/config';
import { getInvitationAcceptUrl } from './mailpit';
import type { TestContext } from './api';

/**
 * Register a new user in a fresh browser context and return the page + context.
 */
export async function registerUser(
    browser: Browser,
    name: string,
    email: string
): Promise<{ page: Page; close: () => Promise<void> }> {
    const context = await browser.newContext();
    const page = await context.newPage();

    await page.goto(PLAYWRIGHT_BASE_URL + '/register');
    await page.getByLabel('Name').fill(name);
    await page.getByLabel('Email').fill(email);
    await page.getByLabel('Password', { exact: true }).fill('amazingpassword123');
    await page.getByLabel('Confirm Password').fill('amazingpassword123');
    await page.getByLabel('I agree to the Terms of').click();
    await page.getByRole('button', { name: 'Register' }).click();
    await page.waitForURL(PLAYWRIGHT_BASE_URL + '/dashboard');

    return { page, close: () => context.close() };
}

/**
 * Invite a user by email from the members page and accept the invitation
 * through a second browser session, returning the accepted member to the
 * members table as a real (non-placeholder) member.
 *
 * @param ownerPage   – The page of the organization owner who sends the invite
 * @param browser     – Browser instance used to create a second context
 * @param memberName  – Display name for the new user
 * @param memberEmail – Email address (must not be registered yet)
 * @param role        – Role button label: 'Employee' | 'Manager' | 'Administrator'
 */
export async function inviteAndAcceptMember(
    ownerPage: Page,
    browser: Browser,
    memberName: string,
    memberEmail: string,
    role: 'Employee' | 'Manager' | 'Administrator'
): Promise<void> {
    // 1. Register the second user
    const secondUser = await registerUser(browser, memberName, memberEmail);

    // 2. Send invitation from the owner
    await ownerPage.goto(PLAYWRIGHT_BASE_URL + '/members');
    await ownerPage.getByRole('button', { name: 'Invite Member' }).click();
    await expect(ownerPage.getByPlaceholder('Member Email')).toBeVisible();
    await ownerPage.getByLabel('Email').fill(memberEmail);
    await ownerPage.getByRole('button', { name: role }).click();
    await Promise.all([
        ownerPage.getByRole('button', { name: 'Invite Member', exact: true }).click(),
        expect(ownerPage.getByRole('main')).toContainText(memberEmail),
    ]);

    // 3. Retrieve the acceptance link from Mailpit and accept
    const acceptUrl = await getInvitationAcceptUrl(secondUser.page.request, memberEmail);
    await secondUser.page.goto(acceptUrl);
    await secondUser.page.waitForURL(/dashboard/);

    // 4. Clean up
    await secondUser.close();
}

/**
 * Set up an admin member in the owner's organization.
 * Returns the admin's page, their member ID, and a cleanup function.
 */
export async function setupAdminUser(
    ownerPage: Page,
    ownerCtx: TestContext,
    browser: Browser
): Promise<{
    adminPage: Page;
    adminMemberId: string;
    closeAdmin: () => Promise<void>;
}> {
    const memberId = Math.floor(Math.random() * 100000);
    const memberEmail = `admin+${memberId}@admin-perms.test`;
    const memberName = 'Admin ' + memberId;

    const admin = await registerUser(browser, memberName, memberEmail);

    await ownerPage.goto(PLAYWRIGHT_BASE_URL + '/members');
    await ownerPage.getByRole('button', { name: 'Invite Member' }).click();
    await expect(ownerPage.getByPlaceholder('Member Email')).toBeVisible();
    await ownerPage.getByPlaceholder('Member Email').fill(memberEmail);
    await ownerPage.getByRole('button', { name: 'Administrator' }).click();
    await Promise.all([
        ownerPage.waitForResponse(
            (response) =>
                response.url().includes('/invitations') &&
                response.request().method() === 'POST' &&
                response.status() === 204
        ),
        ownerPage.getByRole('button', { name: 'Invite Member', exact: true }).click(),
    ]);

    const acceptUrl = await getInvitationAcceptUrl(admin.page.request, memberEmail);
    await admin.page.goto(acceptUrl);
    await admin.page.waitForURL(/dashboard/);

    await admin.page.goto(PLAYWRIGHT_BASE_URL + '/dashboard');
    await expect(admin.page.getByTestId('dashboard_view')).toBeVisible({ timeout: 15000 });

    const orgSwitcherText = await admin.page
        .getByTestId('organization_switcher')
        .first()
        .textContent();
    if (!orgSwitcherText?.includes("John's Organization")) {
        const cookies = await admin.page.context().cookies();
        const xsrfCookie = cookies.find((c) => c.name === 'XSRF-TOKEN');
        const xsrfToken = xsrfCookie ? decodeURIComponent(xsrfCookie.value) : '';

        await admin.page.request.put(`${PLAYWRIGHT_BASE_URL}/current-team`, {
            headers: {
                'X-XSRF-TOKEN': xsrfToken,
                Accept: 'text/html',
            },
            data: { team_id: ownerCtx.orgId },
        });

        await admin.page.goto(PLAYWRIGHT_BASE_URL + '/dashboard');
        await expect(admin.page.getByTestId('dashboard_view')).toBeVisible({ timeout: 15000 });
    }

    const membersResponse = await ownerCtx.request.get(
        `${PLAYWRIGHT_BASE_URL}/api/v1/organizations/${ownerCtx.orgId}/members`
    );
    expect(membersResponse.status()).toBe(200);
    const membersBody = await membersResponse.json();
    const adminMember = membersBody.data.find(
        (m: { role: string; name: string }) => m.role === 'admin' && m.name === memberName
    );
    expect(adminMember).toBeTruthy();

    return {
        adminPage: admin.page,
        adminMemberId: adminMember.id,
        closeAdmin: admin.close,
    };
}

/**
 * Set up an employee member in the owner's organization.
 * Returns the employee's page, their member ID, and a cleanup function.
 *
 * The owner page (from the fixture) is used to invite the employee.
 * Test data should be created via the owner's ctx.
 *
 * IMPORTANT: Projects must be created with is_public: true for the employee to see them,
 * or the employee must be added as a project member via createProjectMemberViaApi.
 * Clients are only visible to employees if they have at least one visible project.
 * Tags are visible to all org members with tags:view permission.
 */
export async function setupEmployeeUser(
    ownerPage: Page,
    ownerCtx: TestContext,
    browser: Browser
): Promise<{
    employeePage: Page;
    employeeMemberId: string;
    closeEmployee: () => Promise<void>;
}> {
    const memberId = Math.floor(Math.random() * 100000);
    const memberEmail = `employee+${memberId}@emp-perms.test`;
    const memberName = 'Emp ' + memberId;

    // Register the employee user first
    const employee = await registerUser(browser, memberName, memberEmail);

    // Send invitation from the owner
    await ownerPage.goto(PLAYWRIGHT_BASE_URL + '/members');
    await ownerPage.getByRole('button', { name: 'Invite Member' }).click();
    await expect(ownerPage.getByPlaceholder('Member Email')).toBeVisible();
    await ownerPage.getByPlaceholder('Member Email').fill(memberEmail);
    await ownerPage.getByRole('button', { name: 'Employee' }).click();
    await Promise.all([
        ownerPage.waitForResponse(
            (response) =>
                response.url().includes('/invitations') &&
                response.request().method() === 'POST' &&
                response.status() === 204
        ),
        ownerPage.getByRole('button', { name: 'Invite Member', exact: true }).click(),
    ]);

    // Accept the invitation
    const acceptUrl = await getInvitationAcceptUrl(employee.page.request, memberEmail);
    await employee.page.goto(acceptUrl);
    await employee.page.waitForURL(/dashboard/);

    // Navigate to dashboard explicitly and wait for it to load to ensure the correct org context.
    await employee.page.goto(PLAYWRIGHT_BASE_URL + '/dashboard');
    await expect(employee.page.getByTestId('dashboard_view')).toBeVisible({ timeout: 15000 });

    // Verify we're on the correct organization (John's Organization).
    const orgSwitcherText = await employee.page
        .getByTestId('organization_switcher')
        .first()
        .textContent();
    if (!orgSwitcherText?.includes("John's Organization")) {
        // Switch to the owner's org using the PUT /current-team endpoint
        const cookies = await employee.page.context().cookies();
        const xsrfCookie = cookies.find((c) => c.name === 'XSRF-TOKEN');
        const xsrfToken = xsrfCookie ? decodeURIComponent(xsrfCookie.value) : '';

        await employee.page.request.put(`${PLAYWRIGHT_BASE_URL}/current-team`, {
            headers: {
                'X-XSRF-TOKEN': xsrfToken,
                Accept: 'text/html',
            },
            data: { team_id: ownerCtx.orgId },
        });

        // Reload to pick up the new org
        await employee.page.goto(PLAYWRIGHT_BASE_URL + '/dashboard');
        await expect(employee.page.getByTestId('dashboard_view')).toBeVisible({ timeout: 15000 });
    }

    // Find the employee's member ID in the owner's organization
    const membersResponse = await ownerCtx.request.get(
        `${PLAYWRIGHT_BASE_URL}/api/v1/organizations/${ownerCtx.orgId}/members`
    );
    expect(membersResponse.status()).toBe(200);
    const membersBody = await membersResponse.json();
    const employeeMember = membersBody.data.find(
        (m: { role: string; name: string }) => m.role === 'employee' && m.name === memberName
    );
    expect(employeeMember).toBeTruthy();

    return {
        employeePage: employee.page,
        employeeMemberId: employeeMember.id,
        closeEmployee: employee.close,
    };
}
