import { type Page, type Locator } from '@playwright/test';
import { BasePage } from './base.page';
import * as path from 'path';

/**
 * Page object for file import operations.
 * Handles navigation to import settings and file import functionality.
 */
export class ImportPage extends BasePage {
  /** Import from file button */
  readonly importFromFileBtn: Locator;
  /** Hidden file input element */
  readonly fileInput: Locator;
  /** Import from URL button */
  readonly importFromUrlBtn: Locator;
  /** Export backup button */
  readonly exportBackupBtn: Locator;

  constructor(page: Page) {
    super(page);
    // The import button contains text "Import from file" and has file_download icon
    this.importFromFileBtn = page.locator(
      'file-imex button:has(mat-icon:has-text("file_download"))',
    );
    this.fileInput = page.locator('file-imex input[type="file"]');
    this.importFromUrlBtn = page.locator(
      'file-imex button:has(mat-icon:has-text("cloud_download"))',
    );
    this.exportBackupBtn = page.locator(
      'file-imex button:has(mat-icon:has-text("file_upload"))',
    );
  }

  /**
   * Navigate to the import/export settings page.
   */
  async navigateToImportPage(): Promise<void> {
    await this.page.goto('/#/config', { waitUntil: 'domcontentloaded' });
    await this.page.locator('config-page').waitFor({ state: 'visible', timeout: 10000 });
    // Wait for page content to fully render
    await this.page.waitForTimeout(1000);

    // The file-imex component is now in the "Sync & Backup" tab (5th tab, index 4)
    // Step 1: Click on the "Sync & Backup" tab to navigate to it
    const syncBackupTab = this.page.locator(
      'mat-tab-header .mat-mdc-tab:has(mat-icon:has-text("cloud_sync"))',
    );
    await syncBackupTab.waitFor({ state: 'visible', timeout: 10000 });

    // Ensure tab is scrolled into view and stable before clicking
    await syncBackupTab.scrollIntoViewIfNeeded();
    await this.page.waitForTimeout(200);

    await syncBackupTab.click();

    // Verify the tab became active by checking for aria-selected attribute
    // Retry click if tab didn't become active
    const maxTabRetries = 3;
    for (let i = 0; i < maxTabRetries; i++) {
      const isActive = await syncBackupTab
        .getAttribute('aria-selected')
        .then((val) => val === 'true')
        .catch(() => false);

      if (isActive) {
        break;
      }

      if (i < maxTabRetries - 1) {
        console.log(
          `[ImportPage] Tab click didn't register, retrying (attempt ${i + 2})`,
        );
        await this.page.waitForTimeout(300);
        await syncBackupTab.click();
      }
    }

    // Wait for tab panel content to load
    await this.page.waitForTimeout(500);

    // Step 2: Within the tab, expand the "Import/Export" collapsible section
    const importExportSection = this.page.locator(
      'collapsible:has-text("Import/Export")',
    );
    await importExportSection.waitFor({ state: 'visible', timeout: 10000 });
    await importExportSection.scrollIntoViewIfNeeded();
    await this.page.waitForTimeout(300);

    // Click on the collapsible header to expand it with retry logic
    const collapsibleHeader = importExportSection.locator('.collapsible-header, .header');
    await collapsibleHeader.click();

    // Verify expansion by checking if import button becomes visible
    // Retry if expansion failed
    const maxExpandRetries = 3;
    for (let i = 0; i < maxExpandRetries; i++) {
      const isExpanded = await this.importFromFileBtn
        .waitFor({ state: 'visible', timeout: 2000 })
        .then(() => true)
        .catch(() => false);

      if (isExpanded) {
        break;
      }

      if (i < maxExpandRetries - 1) {
        console.log(
          `[ImportPage] Collapsible expansion failed, retrying (attempt ${i + 2})`,
        );
        await this.page.waitForTimeout(300);
        await collapsibleHeader.click();
      }
    }

    // Now the file-imex component should be visible in the active tab
    await this.importFromFileBtn.waitFor({ state: 'visible', timeout: 10000 });
  }

  /**
   * Import a backup file using the file input.
   * This triggers the native file dialog via Playwright's setInputFiles.
   *
   * @param filePath - Absolute path to the backup JSON file
   */
  async importBackupFile(filePath: string): Promise<void> {
    // Start listening for the import completion signal BEFORE triggering the import.
    // The "[SP_ALL] Load(import) all data" console message fires when
    // BackupService.importCompleteBackup() has persisted to IndexedDB and dispatched to NgRx.
    // We must start listening first because handleFileInput() reads the file asynchronously
    // via FileReader, and the import may complete before we start listening.
    const importCompletePromise = this.page.waitForEvent('console', {
      predicate: (msg) => msg.text().includes('Load(import) all data'),
      timeout: 60000,
    });

    // Set the file on the hidden input element.
    // setInputFiles() triggers the (change) event on the input, which starts the import.
    // Do NOT dispatch an additional change event — that would trigger the import twice.
    await this.fileInput.setInputFiles(filePath);

    // Handle encryption warning dialog if it appears.
    // The dialog may appear several seconds after setInputFiles because FileReader is async.
    // Race: either the dialog appears (handle it) or the import completes without it.
    // Catch the dialog timeout so a slow import (cold dev server, large seed) doesn't
    // reject the race before importCompletePromise has a chance to resolve.
    const encryptionWarning = this.page
      .locator('dialog-import-encryption-warning')
      .first();
    const dialogOrImport = await Promise.race([
      encryptionWarning
        .waitFor({ state: 'visible', timeout: 15000 })
        .then(() => 'dialog' as const)
        .catch(() => 'no_dialog' as const),
      importCompletePromise.then(() => 'import_complete' as const),
    ]);

    if (dialogOrImport === 'dialog') {
      console.log('[ImportPage] Encryption warning dialog appeared - confirming import');
      const confirmBtn = encryptionWarning.locator('button[color="warn"]');
      await confirmBtn.click();
      await encryptionWarning.waitFor({ state: 'hidden', timeout: 10000 });
      // Now wait for the import to actually complete
      await importCompletePromise;
    } else if (dialogOrImport === 'no_dialog') {
      // Dialog didn't appear within 15s — import is slow but may still complete.
      // Wait for the import-complete signal (its own 60s timeout will catch a true hang).
      await importCompletePromise;
    }
    // If 'import_complete', the import finished without showing the dialog

    // Wait for Angular router navigation to TODAY tag (happens right after dispatch)
    const startTime = Date.now();
    const timeout = 10000;
    while (Date.now() - startTime < timeout) {
      const url = this.page.url();
      if (url.includes('tag') && url.includes('TODAY')) {
        break;
      }
      await this.page.waitForTimeout(200);
    }

    await this.page.waitForLoadState('networkidle');
    await this.page.waitForTimeout(500);
  }

  /**
   * Get the path to a test fixture file.
   *
   * @param filename - Name of the fixture file in e2e/fixtures/
   * @returns Absolute path to the fixture file
   */
  static getFixturePath(filename: string): string {
    return path.resolve(__dirname, '..', 'fixtures', filename);
  }
}
