import type { Locator } from '@playwright/test';
import { expect } from '@playwright/test';

import { BaseModal } from './BaseModal';

/**
 * Credential modal component for canvas and credentials interactions.
 * Used within CanvasPage as `n8n.canvas.credentialModal.*`
 * Used within CredentialsPage as `n8n.credentials.modal.*`
 *
 * @example
 * // Access via canvas page or credentials page
 * await n8n.canvas.credentialModal.addCredential();
 * await expect(n8n.canvas.credentialModal.getModal()).toBeVisible();
 */
export class CredentialModal extends BaseModal {
	constructor(private root: Locator) {
		super(root.page());
	}

	getModal(): Locator {
		return this.root;
	}

	getCredentialName(): Locator {
		return this.root.getByTestId('credential-name');
	}

	getNameInput(): Locator {
		return this.getCredentialName().getByTestId('inline-edit-input');
	}

	getCredentialInputs(): Locator {
		return this.root.getByTestId('credential-connection-parameter');
	}

	async waitForModal(): Promise<void> {
		await this.root.waitFor({ state: 'visible' });
	}

	async fillField(key: string, value: string): Promise<void> {
		const parameterInput = this.root.getByTestId(`parameter-input-${key}`);
		const input = parameterInput.locator('input, textarea');
		// Wait for input to be visible before filling
		await input.waitFor({ state: 'visible', timeout: 10000 });
		await input.fill(value);
		await expect(input).toHaveValue(value);
	}

	/**
	 * Switch a credential field to expression mode and fill it with an expression.
	 *
	 * Expression mode is activated by clicking the "Expression" radio button that
	 * appears when hovering over a parameter input.
	 *
	 * @example
	 * await modal.fillExpressionField('value', "{{ $secrets['myVault']['apikey'] }}");
	 */
	async fillExpressionField(key: string, expression: string): Promise<void> {
		const parameterInput = this.root
			.getByTestId('credential-connection-parameter')
			.getByTestId(key);

		// Hover to reveal the Fixed / Expression toggle
		await parameterInput.locator('label').first().hover();
		await parameterInput.getByTestId('parameter-options-container').waitFor({ state: 'visible' });

		// Click the "Expression" radio option
		await parameterInput.getByTestId('radio-button-expression').click();

		// After switching modes, the field becomes a CodeMirror editor
		const cmContent = parameterInput.locator('.cm-content');
		await cmContent.waitFor({ state: 'visible', timeout: 10_000 });
		await cmContent.click();
		await cmContent.fill(expression);
	}

	async fillAllFields(values: Record<string, string>): Promise<void> {
		for (const [key, val] of Object.entries(values)) {
			await this.fillField(key, val);
		}
	}

	getSaveButton(): Locator {
		return this.root.getByTestId('credential-save-button');
	}

	getParameterInputHint(): Locator {
		return this.container.getByTestId('parameter-input-hint');
	}

	/**
	 * Wait for save to fully complete.
	 * After saving (and optional credential testing), the button either shows a
	 * "Saved" label or settles back to a disabled "Save" state.
	 */
	async waitForSaveComplete(): Promise<void> {
		const saveCompleted = this.root.getByText('Saved', { exact: true }).or(
			this.getSaveButton()
				.locator('button[disabled]')
				.filter({ hasText: /^Save$/ }),
		);

		await expect(saveCompleted).toBeVisible({ timeout: 20_000 });
	}

	async save(): Promise<void> {
		await this.getSaveButton().click();
		await this.waitForSaveComplete();
	}

	async close(): Promise<void> {
		const closeBtn = this.root.locator('.el-dialog__close').first();
		if (await closeBtn.isVisible()) {
			await closeBtn.click();
		}
	}

	/**
	 * Add a credential to the modal
	 * @param fields - The fields to fill in the modal
	 * @param options - The options to pass to the modal
	 * @param options.closeDialog - Whether to close the modal after saving
	 * @param options.name - The name of the credential
	 */
	async addCredential(
		fields: Record<string, string>,
		options?: { closeDialog?: boolean; skipSave?: boolean; name?: string },
	): Promise<void> {
		await this.fillAllFields(fields);
		if (options?.name) {
			await this.getCredentialName().click();
			const nameInput = this.getNameInput();
			await nameInput.fill(options.name);
			await nameInput.press('Enter');
			await expect(this.getCredentialName()).toContainText(options.name);
		}

		if (!options?.skipSave) {
			await expect(this.getSaveButton()).toBeEnabled();
			await this.save();
		}

		const shouldClose = options?.closeDialog ?? true;
		if (shouldClose) {
			await this.close();
		}
	}

	get oauthConnectButton() {
		return this.root.getByTestId('quick-connect-button');
	}

	get oauthConnectSuccessBanner() {
		return this.root.getByTestId('oauth-connect-success-banner');
	}

	async editCredential(): Promise<void> {
		await this.root.page().getByTestId('credential-edit-button').click();
	}

	async deleteCredential(): Promise<void> {
		await this.root.page().getByTestId('credential-delete-button').click();
	}

	async confirmDelete(): Promise<void> {
		await this.root.page().getByRole('button', { name: 'Yes' }).click();
	}

	async renameCredential(newName: string): Promise<void> {
		await this.getCredentialName().click();
		await this.getNameInput().fill(newName);
		await this.getNameInput().press('Enter');
	}

	getOAuthRedirectUrl() {
		return this.root.page().getByTestId('oauth-redirect-url');
	}

	getModeSelector() {
		return this.root.getByTestId('credential-mode-selector');
	}

	getModeDropdownTrigger() {
		return this.root.getByTestId('credential-mode-dropdown-trigger');
	}

	async selectAuthTypeFromDropdown(optionName: string | RegExp): Promise<void> {
		await this.getModeDropdownTrigger().click();
		await this.root.page().getByRole('menuitem', { name: optionName }).click();
	}

	async changeTab(tabName: 'Sharing'): Promise<void> {
		await this.root.getByTestId('menu-item').filter({ hasText: tabName }).click();
	}

	/**
	 * Get a specific credential field input
	 */
	getFieldInput(key: string): Locator {
		return this.root.getByTestId(`parameter-input-${key}`).locator('input, textarea');
	}

	/**
	 * Get the users select dropdown in the Sharing tab
	 */
	getUsersSelect(): Locator {
		return this.root.getByTestId('project-sharing-select').filter({ visible: true });
	}

	/**
	 * Get the visible dropdown popper (for sharing dropdown interactions)
	 */
	getVisibleDropdown(): Locator {
		return this.root.page().locator('.el-popper[aria-hidden="false"]');
	}

	/**
	 * Add a user to credential sharing
	 * @param emailOrName - User email or name to share with
	 */
	async addUserToSharing(emailOrName: string): Promise<void> {
		await this.getUsersSelect().click();
		const dropdown = this.getVisibleDropdown();
		// Wait for dropdown content to load
		await dropdown.locator('.el-select-dropdown__item').first().waitFor({ state: 'visible' });

		// Try to find by email or name (personal projects now show "Personal space" instead of email)
		const byEmail = dropdown.getByText(emailOrName.toLowerCase(), { exact: false });
		if ((await byEmail.count()) > 0) {
			await byEmail.click();
		} else {
			// For personal projects, try matching by name part of email
			const namePart = emailOrName.split('@')[0].replace(/[.-]/g, ' ');
			await dropdown.getByText(new RegExp(namePart, 'i')).first().click();
		}
	}

	/**
	 * Save credential sharing (different from regular save - hits /share endpoint)
	 */
	async saveSharing(): Promise<void> {
		const saveBtn = this.getSaveButton();
		await saveBtn.click();

		// Wait for share API call to complete
		await this.root
			.page()
			.waitForResponse(
				(response) =>
					response.url().includes('/rest/credentials/') &&
					response.url().includes('/share') &&
					response.request().method() === 'PUT',
			);

		await this.waitForSaveComplete();
	}
}
