import { type APIRequestContext, type Page, Response, type TestInfo } from '@playwright/test';
import BasePage from './base-page';
import EditorPage from './editor-page';
import { ElementorType, WindowType } from '../types/types';
import { wpCli } from '../assets/wp-cli';
import ApiRequests from '../assets/api-requests';
import path from 'path';
let elementor: ElementorType;

export default class WpAdminPage extends BasePage {
	protected readonly apiRequests: ApiRequests;

	constructor( page: Page, testInfo: TestInfo, apiRequests: ApiRequests ) {
		super( page, testInfo );
		this.apiRequests = apiRequests;
	}

	/**
	 * Go to the WordPress dashboard.
	 *
	 * @return {Promise<void>}
	 */
	async gotoDashboard(): Promise<void> {
		await this.page.goto( '/wp-admin' );
	}

	/**
	 * If not logged in, log in to WordPress. Otherwise, go to the WordPress dashboard.
	 *
	 * @return {Promise<void>}
	 */
	async login(): Promise<void> {
		await this.gotoDashboard();

		const loggedIn = await this.page.$( 'text=Dashboard' );

		if ( loggedIn ) {
			return;
		}

		await this.page.waitForSelector( 'text=Log In' );
		await this.page.fill( 'input[name="log"]', process.env.USERNAME );
		await this.page.fill( 'input[name="pwd"]', process.env.PASSWORD );
		await this.page.click( 'text=Log In' );
		await this.page.waitForSelector( 'text=Dashboard' );
	}

	/**
	 * Log in to WordPress with custom credentials.
	 *
	 * @param {string} username - The username to log in with.
	 * @param {string} password - The password to log in with.
	 *
	 * @return {Promise<void>}
	 */
	async customLogin( username: string, password: string ): Promise<void> {
		await this.gotoDashboard();
		const loggedIn = await this.page.$( 'text=Dashboard' );

		if ( loggedIn ) {
			await this.page.hover( '#wp-admin-bar-top-secondary' );
			await this.page.click( '#wp-admin-bar-logout > a' );
		}

		await this.page.fill( 'input[name="log"]', username );
		await this.page.fill( 'input[name="pwd"]', password );
		await this.page.locator( 'text=Log In' ).last().click();
		await this.page.waitForSelector( 'text=Dashboard' );
	}

	/**
	 * Open a new Elementor page.
	 *
	 * @param {boolean} setWithApi  - Optional. Whether to create the page with the API. Default is true.
	 * @param {boolean} setPageName - Optional. Whether to set the page name. Default is true.
	 *
	 * @return {Promise<EditorPage>} A promise that resolves to the new editor page instance.
	 */
	async openNewPage( setWithApi: boolean = true, setPageName: boolean = true ): Promise<EditorPage> {
		let postId: number | null = null;

		if ( setWithApi ) {
			const postIdString = await this.createNewPostWithAPI();
			postId = parseInt( postIdString, 10 );
		} else {
			await this.createNewPostFromDashboard( setPageName );
			const postIdString = await this.page.evaluate( () => {
				const urlParams = new URLSearchParams( window.location.search );
				return urlParams.get( 'post' );
			} );
			if ( postIdString ) {
				postId = parseInt( postIdString, 10 );
			}
		}

		await this.page.waitForLoadState( 'load', { timeout: 20000 } );
		await this.waitForPanel();
		await this.closeAnnouncementsIfVisible();

		return new EditorPage( this.page, this.testInfo, postId );
	}

	/**
	 * Create a new page with the API and open it in Elementor.
	 *
	 * @return {Promise<string>} A promise that resolves to the created page ID.
	 */
	async createNewPostWithAPI(): Promise<string> {
		const request: APIRequestContext = this.page.context().request,
			postDataInitial = {
				title: 'Playwright Test Page - Uninitialized',
				content: '',
			},
			postId = await this.apiRequests.create( request, 'pages', postDataInitial ),
			postDataUpdated = {
				title: `Playwright Test Page #${ postId }`,
			};

		await this.apiRequests.create( request, `pages/${ postId }`, postDataUpdated );
		await this.page.goto( `/wp-admin/post.php?post=${ postId }&action=elementor` );

		return postId;
	}

	/**
	 * Create a new page from the WordPress dashboard.
	 *
	 * @param {boolean} setPageName - Whether to set the page name.
	 *
	 * @return {Promise<void>}
	 */
	async createNewPostFromDashboard( setPageName: boolean ): Promise<void> {
		if ( ! await this.page.$( '.e-overview__create > a' ) ) {
			await this.gotoDashboard();
		}

		await this.page.click( '.e-overview__create > a' );

		if ( ! setPageName ) {
			return;
		}

		await this.setPageName();
	}

	/**
	 * Set the page name.
	 *
	 * @return {Promise<void>}
	 */
	async setPageName(): Promise<void> {
		await this.page.locator( '#elementor-panel-footer-settings' ).click();

		const pageId = await this.page.evaluate( () => elementor.config.initialDocument.id );
		await this.page.locator( '.elementor-control-post_title input' ).fill( `Playwright Test Page #${ pageId }` );

		await this.page.locator( '#elementor-panel-footer-saver-options' ).click();
		await this.page.locator( '#elementor-panel-footer-sub-menu-item-save-draft' ).click();
		await this.page.locator( '#elementor-panel-header-add-button' ).click();
	}

	/**
	 * Convert the current page from Gutenberg to Elementor.
	 *
	 * @return {Promise<EditorPage>} A promise that resolves to the editor page instance.
	 */
	async convertFromGutenberg(): Promise<EditorPage> {
		await Promise.all( [
			this.page.waitForResponse( async ( response ) => await this.blockUrlResponse( response ) ),
			this.page.click( '#elementor-switch-mode' ),
		] );

		await this.page.waitForURL( '**/post.php?post=*&action=elementor' );
		await this.page.waitForLoadState( 'load', { timeout: 20000 } );
		await this.waitForPanel();

		await this.closeAnnouncementsIfVisible();

		const postIdString = await this.page.evaluate( () => {
			const urlParams = new URLSearchParams( window.location.search );
			return urlParams.get( 'post' );
		} );
		const postId = postIdString ? parseInt( postIdString, 10 ) : null;

		return new EditorPage( this.page, this.testInfo, postId );
	}

	/**
	 * Get the response status for the API request.
	 *
	 * @param {Response} response - The response object.
	 *
	 * @return {Promise<boolean>} A promise that resolves to true if the response is a valid REST/JSON request with a 200 status.
	 */
	async blockUrlResponse( response: Response ): Promise<boolean> {
		const isRestRequest = response.url().includes( 'rest_route=%2Fwp%2Fv2%2Fpages%2' ); // For local testing
		const isJsonRequest = response.url().includes( 'wp-json/wp/v2/pages' ); // For CI testing
		return ( isJsonRequest || isRestRequest ) && 200 === response.status();
	}

	/**
	 * Wait for the Elementor editor panel to finish loading.
	 *
	 * @return {Promise<void>}
	 */
	async waitForPanel(): Promise<void> {
		await this.page.waitForSelector( '.elementor-panel-loading', { state: 'detached' } );
		await this.page.waitForSelector( '#elementor-loading', { state: 'hidden' } );
	}

	/**
	 * Activate and deactivate Elementor experiments.
	 *
	 * TODO: The testing environment isn't clean between tests - Use with caution!
	 *
	 * @param {Object}            experiments - Experiments settings ( `{ experiment_id: true / false }` );
	 * @param {(boolean|string)=} oldUrl      - Optional. Whether to use the old URL structure. Default is false.
	 *
	 * @return {Promise<void>}
	 */
	async setExperiments( experiments: { [ n: string ]: boolean | string }, oldUrl: boolean = false ): Promise<void> {
		if ( oldUrl ) {
			await this.page.goto( '/wp-admin/admin.php?page=elementor#tab-experiments' );
			await this.page.click( '#elementor-settings-tab-experiments' );
		} else {
			await this.page.goto( '/wp-admin/admin.php?page=elementor-settings#tab-experiments' );
		}

		const prefix = 'e-experiment';

		for ( const [ id, state ] of Object.entries( experiments ) ) {
			const selector = `#${ prefix }-${ id }`;

			// Try to make the element visible - Since some experiments may be hidden for the user,
			// but actually exist and need to be tested.
			await this.page.evaluate( ( el ) => {
				const element: HTMLElement = document.querySelector( el );

				if ( element ) {
					element.style.display = 'block';
				}
			}, `.elementor_experiment-${ id }` );

			await this.page.selectOption( selector, state ? 'active' : 'inactive' );

			// Click to confirm any experiment that has dependencies.
			await this.confirmExperimentModalIfOpen();
		}

		await this.page.click( '#submit' );
	}

	/**
	 * Reset all Elementor experiments to their default settings.
	 *
	 * @return {Promise<void>}
	 */
	async resetExperiments(): Promise<void> {
		await this.page.goto( '/wp-admin/admin.php?page=elementor-settings#tab-experiments' );
		await this.page.getByRole( 'button', { name: 'default' } ).click();
	}

	/**
	 * Set site language.
	 *
	 * @param {string}      language     - The site language to set.
	 * @param {string|null} userLanguage - Optional. The user language to set. Default is null.
	 *
	 * @return {Promise<void>}
	 */
	async setSiteLanguage( language: string, userLanguage: string = null ): Promise<void> {
		let languageCheck = language;

		if ( 'he_IL' === language ) {
			languageCheck = 'he-IL';
		} else if ( '' === language ) {
			languageCheck = 'en_US';
		}

		await this.page.goto( '/wp-admin/options-general.php' );

		const isLanguageActive = await this.page.locator( 'html[lang=' + languageCheck + ']' ).isVisible();

		if ( ! isLanguageActive ) {
			await this.page.selectOption( '#WPLANG', language );
			await this.page.locator( '#submit' ).click();
		}

		const userProfileLanguage = null !== userLanguage ? userLanguage : language;
		await this.setUserLanguage( userProfileLanguage );
	}

	/**
	 * Set user language.
	 *
	 * @param {string} language - The language to set.
	 *
	 * @return {Promise<void>}
	 */
	async setUserLanguage( language: string ): Promise<void> {
		await this.page.goto( 'wp-admin/profile.php' );
		await this.page.selectOption( '[name="locale"]', language );
		await this.page.locator( '#submit' ).click();
	}

	/**
	 * Confirm the Elementor experiment modal if it's open.
	 *
	 * @return {Promise<void>}
	 */
	async confirmExperimentModalIfOpen(): Promise<void> {
		const dialogButton = this.page.locator( '.dialog-type-confirm .dialog-confirm-ok' );

		if ( await dialogButton.isVisible() ) {
			await dialogButton.click();

			// Clicking the confirm button - "Activate" or "Deactivate" - will immediately save the existing experiments,
			// so we need to wait for the page to save and reload before we continue on to set any more experiments.
			await this.page.waitForLoadState( 'load' );
		}
	}

	/**
	 * Get the active WordPress theme.
	 *
	 * @return {Promise<string>} The name of the active WordPress theme.
	 */
	async getActiveTheme(): Promise<string> {
		const request: APIRequestContext = this.page.context().request;
		const themeData = await this.apiRequests.getTheme( request, 'active' );
		return themeData[ 0 ].stylesheet;
	}

	async activateTheme( theme: string ) {
		await wpCli( `wp theme activate ${ theme }` );
	}

	/**
	 * Enable uploading SVG files.
	 *
	 * @return {Promise<void>}
	 */
	async enableAdvancedUploads(): Promise<void> {
		await this.page.goto( '/wp-admin/admin.php?page=elementor-settings#tab-advanced' );
		await this.page.locator( 'select[name="elementor_unfiltered_files_upload"]' ).selectOption( '1' );
		await this.page.getByRole( 'button', { name: 'Save Changes' } ).click();
	}

	/**
	 * Disable uploading SVG files.
	 *
	 * @return {Promise<void>}
	 */
	async disableAdvancedUploads(): Promise<void> {
		await this.page.goto( '/wp-admin/admin.php?page=elementor-settings#tab-advanced' );
		await this.page.locator( 'select[name="elementor_unfiltered_files_upload"]' ).selectOption( '' );
		await this.page.getByRole( 'button', { name: 'Save Changes' } ).click();
	}

	/**
	 * Close the Elementor announcements if they are visible.
	 *
	 * @return {Promise<void>}
	 */
	async closeAnnouncementsIfVisible(): Promise<void> {
		if ( await this.page.locator( '#e-announcements-root' ).count() > 0 ) {
			await this.page.evaluate( ( selector ) => document.getElementById( selector ).remove(), 'e-announcements-root' );
		}
		let window: WindowType;
		await this.page.evaluate( () => {
			// eslint-disable-next-line @typescript-eslint/ban-ts-comment
			// @ts-ignore editor session is on the window object
			const editorSessionId = window.EDITOR_SESSION_ID;
			window.sessionStorage.setItem( 'ai_promotion_introduction_editor_session_key', editorSessionId );
		} );
	}

	/**
	 * Edit the page with Elementor.
	 *
	 * @return {Promise<void>}
	 */
	async editWithElementor(): Promise<void> {
		await this.page.getByRole( 'link', { name: ' Edit with Elementor' } ).click();
	}

	/**
	 * Close the block editor popup if it's visible.
	 *
	 * @return {Promise<void>}
	 */
	async closeBlockEditorPopupIfVisible(): Promise<void> {
		await this.page.locator( '#elementor-switch-mode-button' ).waitFor();
		if ( await this.page.getByRole( 'button', { name: 'Close' } ).isVisible() ) {
			await this.page.getByRole( 'button', { name: 'Close' } ).click();
		}
	}

	/**
	 * Open a new WordPress page.
	 *
	 * @return {Promise<void>}
	 */
	async openNewWordpressPage(): Promise<void> {
		await this.page.goto( '/wp-admin/post-new.php?post_type=page' );
		await this.closeBlockEditorPopupIfVisible();
	}

	/**
	 * Hide the WordPress admin bar.
	 *
	 * @return {Promise<void>}
	 */
	async hideAdminBar(): Promise<void> {
		await this.page.goto( '/wp-admin/profile.php' );
		await this.page.locator( '#admin_bar_front' ).uncheck();
		await this.page.locator( '#submit' ).click();
	}

	/**
	 * Show the WordPress admin bar.
	 *
	 * @return {Promise<void>}
	 */
	async showAdminBar(): Promise<void> {
		await this.page.goto( '/wp-admin/profile.php' );
		await this.page.locator( '#admin_bar_front' ).check();
		await this.page.locator( '#submit' ).click();
	}

	/**
	 * Wait for the Elementor editor to finish loading.
	 *
	 * @return {Promise<void>}
	 */
	async waitForEditorToLoad(): Promise<void> {
		await this.page.waitForLoadState( 'load', { timeout: 20000 } );
		await this.waitForPanel();
	}

	async createNewMenu( menuName: string ) {
		await this.deleteAllMenus();

		await this.page.goto( '/wp-admin/nav-menus.php' );
		await this.page.waitForLoadState( 'load' );

		if ( await this.page.getByRole( 'link', { name: 'create a new menu' } ).isVisible() ) {
			await this.page.getByRole( 'link', { name: 'create a new menu' } ).click();
		}

		await this.page.getByRole( 'textbox', { name: 'Menu Name' } ).click();
		await this.page.getByRole( 'textbox', { name: 'Menu Name' } ).fill( menuName );
		await this.page.getByRole( 'textbox', { name: 'Menu Name' } ).press( 'Enter' );
		await this.page.getByRole( 'checkbox', { name: 'Header' } ).check();

		if ( await this.page.getByRole( 'button', { name: 'Create Menu' } ).isVisible() ) {
			await this.page.getByRole( 'button', { name: 'Create Menu' } ).click();
		} else {
			await this.page.getByRole( 'button', { name: 'Save Menu' } ).click();
		}

		await this.page.getByRole( 'button', { name: 'Custom Links' } ).click();
		await this.page.getByRole( 'textbox', { name: 'URL' } ).click();
		await this.page.getByRole( 'textbox', { name: 'URL' } ).fill( '#' );
		await this.page.getByRole( 'textbox', { name: 'URL' } ).press( 'Tab' );
		await this.page.getByRole( 'textbox', { name: 'Link Text' } ).fill( 'Parent menu item' );
		await this.page.getByRole( 'textbox', { name: 'Link Text' } ).press( 'Enter' );
		await this.page.getByRole( 'textbox', { name: 'URL' } ).click();
		await this.page.getByRole( 'textbox', { name: 'URL' } ).fill( '#' );
		await this.page.getByRole( 'textbox', { name: 'URL' } ).press( 'Tab' );
		await this.page.getByRole( 'textbox', { name: 'Link Text' } ).fill( 'Child menu item' );
		await this.page.getByRole( 'textbox', { name: 'Link Text' } ).press( 'Enter' );

		await this.page.waitForTimeout( 1000 );

		const itemOne = this.page.locator( '#menu-to-edit > li:nth-child(1) .menu-item-handle' );
		const itemTwo = this.page.locator( '#menu-to-edit > li:nth-child(2) .menu-item-handle' );

		const itemOneBox = await itemOne.boundingBox();
		const itemTwoBox = await itemTwo.boundingBox();

		if ( itemOneBox && itemTwoBox ) {
			// Drag `two` near and slightly right below `one` to make it a child
			await this.page.mouse.move(
				itemTwoBox.x + ( itemTwoBox.width / 2 ),
				itemTwoBox.y + ( itemTwoBox.height / 2 ),
			);
			await this.page.mouse.down();
			await this.page.mouse.move(
				itemOneBox.x + 30, // ← indent to the right to trigger submenu nesting
				itemOneBox.y + ( itemOneBox.height + 10 ),
				{ steps: 10 },
			);
			await this.page.mouse.up();
		}

		await this.page.getByRole( 'button', { name: 'Save Menu' } ).click();
	}

	/**
	 * Import WordPress XML file using the WordPress importer.
	 *
	 * @param {string} filePath - Path to the XML file to import.
	 * @param {Page}   page     - Optional. Page instance to use. Defaults to this.page.
	 * @return {Promise<void>}
	 */
	async importWordPressXml( filePath: string, page: Page = this.page ): Promise<void> {
		await page.goto( '/wp-admin/import.php?import=wordpress' );
		await page.waitForLoadState( 'load' );
		await this.dismissEditorOnePointerIfVisible();

		await page.setInputFiles( 'input[name="import"]', filePath );

		await Promise.all( [
			page.waitForNavigation(),
			page.click( 'input:has-text("Upload file and import")' ),
		] );

		await page.selectOption( 'select[name="user_map[0]"]', '1' );
		await page.check( 'input[name="fetch_attachments"]' );
		await page.click( 'text=Submit' );
		await page.click( 'text=All done. Have fun!' );
	}

	/**
	 * Import menu from XML file.
	 *
	 * @return {Promise<void>}
	 */
	async importMenu(): Promise<void> {
		const menuImportPath = path.resolve( __dirname, '..', 'sample-data', 'menu-import.xml' );
		await this.importWordPressXml( menuImportPath );
	}

	/**
	 * Delete all existing Menus.
	 * Loops all the menus, and delete them one by one until the delete menu button disappears.
	 */
	async deleteAllMenus(): Promise<void> {
		await this.page.goto( '/wp-admin/nav-menus.php' );
		await this.page.waitForLoadState( 'load' );

		const deleteMenuButton = await this.page.$( '#nav-menu-footer .menu-delete' );
		// If the 'delete menu' button exists, delete the current menu.
		if ( deleteMenuButton ) {
			const deleteHref: string | null = await this.page.evaluate( () => document.querySelector( '#nav-menu-footer .menu-delete' )!.getAttribute( 'href' ) );
			const page2 = await this.page.context().newPage();

			await page2.goto( deleteHref! );
			await page2.close();
			await this.deleteAllMenus();
		}
	}

	async dismissEditorOnePointerIfVisible(): Promise<void> {
		const pointer = this.page.locator( '.wp-pointer' );
		if ( ! await pointer.isVisible() ) {
			return;
		}

		const hasEditorOneTitle = await pointer.locator( 'h3:has-text("The Editor has a new home")' ).isVisible();
		if ( ! hasEditorOneTitle ) {
			return;
		}

		await pointer.locator( '.wp-pointer-buttons a.close' ).click();
		await pointer.waitFor( { state: 'hidden' } );
	}
}
