import path from 'path';
import { Page, Locator, expect } from '@playwright/test';

async function isVisible(locator: Locator, timeout = 1000): Promise<boolean> {
    return locator.isVisible({ timeout }).catch(() => false);
}

async function waitForAnyVisible(locators: Locator[], timeout = 120000): Promise<void> {
    const deadline = Date.now() + timeout;

    while (Date.now() < deadline) {
        for (const locator of locators) {
            if (await isVisible(locator, 500)) {
                return;
            }
        }

        await locators[0].page().waitForTimeout(500);
    }

    throw new Error('Expected one of the target elements to become visible.');
}

export class DatasetCreationProcess {
    readonly page: Page;
    readonly chatInput: Locator;
    readonly sendButton: Locator;
    readonly welcomePopup: Locator;
    readonly welcomePopupBanner: Locator;
    readonly dontShowAgainCheckbox: Locator;
    readonly namePrompt: Locator;
    readonly descriptionPrompt: Locator;
    readonly fileUploadButton: Locator;
    readonly fileInput: Locator;
    readonly confirmButton: Locator;
    readonly schemaSearchInput: Locator;
    readonly openStudioButton: Locator;
    readonly maximizeWidgetButton: Locator;
    readonly saveButton: Locator;
    readonly thumbnailButton: Locator;
    readonly createButton: Locator;
    readonly publishButton: Locator;
    readonly singleUsePriceInput: Locator;
    readonly fiveUsesPriceInput: Locator;
    readonly annualPriceInput: Locator;
    readonly unlimitedPriceInput: Locator;
    readonly reviewCancelButton: Locator;
    readonly closeNotificationButton: Locator;
    readonly publishSuccessState: Locator;
    readonly assetCreatedSuccessState: Locator;

    readonly datasetFile: string;
    readonly thumbnailFile: string;

    constructor(page: Page) {
        this.page = page;

        this.chatInput = page.getByPlaceholder('Ask anything...').first();
        this.sendButton = page.locator('#chat-input').getByRole('button').first();
        this.welcomePopup = page.getByText(/Welcome to RoboCorp!/i).first();
        this.welcomePopupBanner = page.locator('#welcome-cards-banner').first();
        this.dontShowAgainCheckbox = page.getByRole('checkbox', { name: /don't show again/i }).first();
        this.namePrompt = page.getByText(
            /dataset name|name of your dataset|name of your new dataset|name of the data set/i
        ).first();
        this.descriptionPrompt = page.getByText(/dataset description|please describe|description/i).first();
        this.fileUploadButton = page.getByRole('button', { name: /Select file to upload/i }).first();
        this.fileInput = page.locator('input[type="file"]').first();
        this.confirmButton = page.getByRole('button', { name: /^Confirm$/i }).last();
        this.schemaSearchInput = page.getByRole('textbox', { name: /Search options/i }).first();
        this.openStudioButton = page.getByRole('button', { name: /Open Studio/i }).first();
        this.maximizeWidgetButton = page.getByRole('button', { name: /Maximize widget/i }).first();
        this.saveButton = page.getByRole('button', { name: /^Save$/i }).last();
        this.thumbnailButton = page.getByRole('button', { name: /^Thumbnail$/i }).first();
        this.createButton = page.getByRole('button', { name: /^Create$/i }).last();
        this.publishButton = page.getByRole('button', { name: /^Publish$/i }).first();
        this.singleUsePriceInput = page.locator('#sabw_form-field-single-use').getByRole('textbox').first();
        this.fiveUsesPriceInput = page.locator('#sabw_form-field-five-uses').getByRole('textbox').first();
        this.annualPriceInput = page.locator('#sabw_form-field-annual input[type="text"]').first();
        this.unlimitedPriceInput = page
            .locator('[id="sabw_form-field-unlimited-(including-for-reselling)"] input[type="text"]')
            .first();
        this.reviewCancelButton = page.locator('#btn_sabw_review_publish_cancel').first();
        this.closeNotificationButton = page.getByLabel(/Close notification/i).first();
        this.publishSuccessState = page.locator('body').getByText(/published|success|successfully/i).first();
        this.assetCreatedSuccessState = page.getByText('Asset created successfully!').first();

        this.datasetFile = path.resolve(__dirname, '../asset-creation-files/dataset.csv');
        this.thumbnailFile = path.resolve(__dirname, '../asset-creation-files/asset-image.jpg');
    }

    private async waitForChatInputReady(timeout = 90000) {
        await expect(this.chatInput).toBeVisible({ timeout });

        const deadline = Date.now() + timeout;
        while (Date.now() < deadline) {
            const disabled = await this.chatInput.evaluate((node) => {
                const element = node as HTMLTextAreaElement | HTMLInputElement;
                return element.disabled;
            }).catch(() => true);

            if (!disabled) {
                await expect(this.chatInput).toBeEditable({ timeout: 5000 });
                return;
            }

            await this.page.waitForTimeout(500);
        }

        throw new Error('Chat input did not become editable within the expected time.');
    }

    private async sendChatMessage(message: string) {
        await this.waitForChatInputReady();
        await this.chatInput.fill(message);
        await this.chatInput.press('Enter').catch(async () => {
            await this.sendButton.click();
        });
    }

    async closeWelcomePopupIfVisible() {
        await this.welcomePopup.waitFor({ state: 'visible', timeout: 8000 }).catch(() => null);

        if (!(await isVisible(this.welcomePopup, 1000))) {
            return;
        }

        if (await isVisible(this.dontShowAgainCheckbox, 1000)) {
            await this.dontShowAgainCheckbox.click({ force: true }).catch(() => {});
        }

        const closeCandidates = [
            this.page.locator('#welcome-cards-banner button.absolute').first(),
            this.welcomePopupBanner.locator('button.absolute').first(),
            this.welcomePopupBanner.locator('button').last(),
            this.page.getByRole('button', { name: /close/i }).first(),
        ];

        for (const closeButton of closeCandidates) {
            if (!(await isVisible(closeButton, 1000))) {
                continue;
            }

            await closeButton.click({ force: true }).catch(() => {});
            if (await isVisible(this.welcomePopup, 1000)) {
                await closeButton.evaluate((node) => {
                    (node as HTMLButtonElement).click();
                }).catch(() => {});
            }

            if (!(await isVisible(this.welcomePopup, 1500))) {
                return;
            }
        }

        await this.page.keyboard.press('Escape').catch(() => {});
        await expect(this.welcomePopup).not.toBeVisible({ timeout: 3000 });
    }

    async startDatasetCreation() {
        await this.closeWelcomePopupIfVisible();
        await this.waitForChatInputReady();

        await this.sendChatMessage('I need to create a dataset without a domain');

        await expect(this.namePrompt).toBeVisible({ timeout: 180000 });
    }

    async provideDatasetName(name = `Automation Dataset creation ${new Date().toISOString()}`) {
        await expect(this.namePrompt).toBeVisible({ timeout: 180000 });
        await this.sendChatMessage(name);
        await expect(this.descriptionPrompt).toBeVisible({ timeout: 180000 });
    }

    async provideDescription(description = 'Automated dataset creation flow description.') {
        await expect(this.descriptionPrompt).toBeVisible({ timeout: 180000 });
        await this.sendChatMessage(description);
        await expect(this.fileUploadButton).toBeVisible({ timeout: 180000 });
    }

    async uploadDataset() {
        await expect(this.fileUploadButton).toBeVisible({ timeout: 180000 });
        await expect(this.fileInput).toBeAttached({ timeout: 60000 });
        await this.fileInput.setInputFiles(this.datasetFile);

        await expect(this.confirmButton).toBeEnabled({ timeout: 30000 });
        await this.confirmButton.click();
    }

    async chooseSchema() {
        const schemaPrompt = this.page.getByText(/select one or more schema/i).first();
        const selectionButton = this.page.locator('#btn_selection_widget_button').first();
        const confirmSelectionButton = this.page.getByRole('button', { name: /^Confirm$/i }).last();
        const firstOptionCheckbox = this.page.locator('#selection_widget_opt_0').getByRole('checkbox').first();
        const firstCheckbox = this.page.getByRole('checkbox').first();
        const firstRadio = this.page.getByRole('radio').first();

        await waitForAnyVisible(
            [schemaPrompt, selectionButton, this.openStudioButton, this.maximizeWidgetButton],
            180000
        );

        if (!(await isVisible(schemaPrompt, 2000)) && !(await isVisible(selectionButton, 2000))) {
            return;
        }

        let selected = false;
        if (await isVisible(firstOptionCheckbox, 10000)) {
            await firstOptionCheckbox.click({ force: true }).catch(() => {});
            selected = await firstOptionCheckbox.isChecked().catch(async () => {
                const checked = await firstOptionCheckbox.getAttribute('aria-checked').catch(() => null);
                return checked === 'true';
            });
        } else if (await isVisible(firstCheckbox, 10000)) {
            await firstCheckbox.click({ force: true }).catch(() => {});
            selected = await firstCheckbox.isChecked().catch(async () => {
                const checked = await firstCheckbox.getAttribute('aria-checked').catch(() => null);
                return checked === 'true';
            });
        } else if (await isVisible(firstRadio, 10000)) {
            await firstRadio.click({ force: true }).catch(() => {});
            selected = await firstRadio.isChecked().catch(async () => {
                const checked = await firstRadio.getAttribute('aria-checked').catch(() => null);
                return checked === 'true';
            });
        }

        if (!selected) {
            if (await isVisible(this.schemaSearchInput, 5000)) {
                await this.schemaSearchInput.press('ArrowDown').catch(() => {});
                await this.schemaSearchInput.press('Space').catch(() => {});
                selected = await confirmSelectionButton.isEnabled({ timeout: 2000 }).catch(() => false);
            }
        }

        if (!selected) {
            throw new Error('No selectable schema option was available for dataset creation.');
        }

        if (await isVisible(confirmSelectionButton, 5000)) {
            await expect(confirmSelectionButton).toBeEnabled({ timeout: 10000 });
            await confirmSelectionButton.click().catch(() => {});
        } else if (await isVisible(selectionButton, 5000)) {
            await selectionButton.click();
        }

        await waitForAnyVisible(
            [this.openStudioButton, this.maximizeWidgetButton, this.saveButton],
            120000
        );
    }

    async completeStudioSetup() {
        if (await isVisible(this.openStudioButton, 10000)) {
            await this.openStudioButton.click();
        }

        await waitForAnyVisible(
            [this.maximizeWidgetButton, this.saveButton, this.page.getByText(/Data Studio|Purpose/i).first()],
            180000
        );

        await expect(this.saveButton).toBeVisible({ timeout: 120000 });
        if (await this.saveButton.isEnabled({ timeout: 5000 }).catch(() => false)) {
            await this.saveButton.click();
        }

        await expect(this.thumbnailButton).toBeVisible({ timeout: 120000 });
        await this.thumbnailButton.click();

        const thumbnailInput = this.page.locator('input[type="file"]').last();
        await expect(thumbnailInput).toBeAttached({ timeout: 30000 });
        await thumbnailInput.setInputFiles(this.thumbnailFile);

        if (await this.saveButton.isEnabled({ timeout: 5000 }).catch(() => false)) {
            await this.saveButton.click();
        }

        if (await this.saveButton.isEnabled({ timeout: 5000 }).catch(() => false)) {
            await this.saveButton.click();
        }
    }

    async createDataset() {
        await expect(this.createButton).toBeVisible({ timeout: 120000 });
        await expect(this.createButton).toBeEnabled({ timeout: 120000 });
        await this.createButton.click();
    }

    async publishDataset() {
        await expect(this.publishButton).toBeVisible({ timeout: 120000 });
        await this.publishButton.click();

        await waitForAnyVisible(
            [this.assetCreatedSuccessState, this.publishSuccessState, this.singleUsePriceInput],
            120000
        );

        const assetCreatedVisible = await this.assetCreatedSuccessState.isVisible({ timeout: 1000 }).catch(() => false);
        const publishSuccessVisible = await this.publishSuccessState.isVisible({ timeout: 1000 }).catch(() => false);

        if (assetCreatedVisible || publishSuccessVisible) {
            return;
        }

        try {
            await expect(this.singleUsePriceInput).toBeVisible({ timeout: 5000 });
            await this.singleUsePriceInput.fill('1');
            await this.fiveUsesPriceInput.fill('23');
            await this.annualPriceInput.fill('3');
            await this.unlimitedPriceInput.fill('4');

            if (await isVisible(this.reviewCancelButton, 5000)) {
                await this.reviewCancelButton.click();
            }
        } catch {
            const successAfterFillRace = await Promise.any([
                this.assetCreatedSuccessState.waitFor({ state: 'visible', timeout: 10000 }).then(() => true),
                this.publishSuccessState.waitFor({ state: 'visible', timeout: 10000 }).then(() => true),
                expect(this.page.getByText('Asset created successfully!')).toBeVisible({ timeout: 10000 }).then(() => true)
            ]).catch(() => false);

            if (!successAfterFillRace) {
                throw new Error('Dataset publish did not show a stable pricing form or success state.');
            }
            return;
        }

        await Promise.race([
            this.assetCreatedSuccessState.waitFor({ state: 'visible', timeout: 120000 }).catch(() => null),
            this.publishSuccessState.waitFor({ state: 'visible', timeout: 120000 }).catch(() => null),
            expect(this.page.getByText('Asset created successfully!')).toBeVisible({ timeout: 120000 }),
            expect(this.page.locator('body')).toContainText(/published|success|successfully/i, { timeout: 120000 }),
            expect(this.publishButton).toBeHidden({ timeout: 120000 })
        ]);
    }
}
