import type { Page } from 'playwright';
import type { NavigationResultDTO } from '../../../../application/dto/NavigationResultDTO';
import type { WaitResultDTO } from '../../../../application/dto/WaitResultDTO';
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
import type { PlaywrightConfig } from '../core/PlaywrightAutomationAdapter';
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
import { IRACING_SELECTORS, IRACING_TIMEOUTS, IRACING_URLS } from './RacingSelectors';

export class IRacingDomNavigator {
  private static readonly STEP_TO_PAGE_MAP: Record<number, string> = {
    7: 'timeLimit',
    8: 'cars',
    9: 'cars',
    10: 'carClasses',
    11: 'track',
    12: 'track',
    13: 'trackOptions',
    14: 'timeOfDay',
    15: 'weather',
    16: 'raceOptions',
    17: 'trackConditions',
  };

  constructor(
    private readonly config: Required<PlaywrightConfig>,
    private readonly browserSession: PlaywrightBrowserSession,
    private readonly logger?: LoggerPort,
    private readonly onWizardDismissed?: () => Promise<void>,
  ) {}

  private log(level: 'debug' | 'info' | 'warn' | 'error', message: string, context?: Record<string, unknown>): void {
    if (!this.logger) {
      return;
    }
    const logger = this.logger as Record<
      'debug' | 'info' | 'warn' | 'error',
      (msg: string, ctx?: Record<string, unknown>) => void
    >;
    logger[level](message, context);
  }

  private isRealMode(): boolean {
    return this.config.mode === 'real';
  }

  private getPage(): Page | null {
    return this.browserSession.getPage();
  }

  async navigateToPage(url: string): Promise<NavigationResultDTO> {
    const page = this.getPage();
    if (!page) {
      return { success: false, url, loadTime: 0, error: 'Browser not connected' };
    }

    const startTime = Date.now();

    try {
      const targetUrl = this.isRealMode() && !url.startsWith('http') ? IRACING_URLS.hostedSessions : url;
      const timeout = this.isRealMode() ? IRACING_TIMEOUTS.navigation : this.config.timeout;

      this.log('debug', 'Navigating to page', { url: targetUrl, mode: this.config.mode });
      await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout });

      const loadTime = Date.now() - startTime;

      if (!this.isRealMode()) {
        const stepMatch = url.match(/step-(\d+)-/);
        if (stepMatch) {
          const [, stepStr] = stepMatch;
          if (stepStr) {
            const stepNumber = parseInt(stepStr, 10);
            await page.evaluate((step) => {
              document.body.setAttribute('data-step', String(step));
            }, stepNumber);
          }
        }
      }

      return { success: true, url: targetUrl, loadTime };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      const loadTime = Date.now() - startTime;
      return { success: false, url, loadTime, error: message };
    }
  }

  async waitForElement(target: string, maxWaitMs?: number): Promise<WaitResultDTO> {
    const page = this.getPage();
    if (!page) {
      return { success: false, target, waitedMs: 0, found: false, error: 'Browser not connected' };
    }

    const startTime = Date.now();
    const defaultTimeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;

    try {
      let selector: string;
      if (target.startsWith('[') || target.startsWith('button') || target.startsWith('#')) {
        selector = target;
      } else {
        selector = IRACING_SELECTORS.wizard.modal;
      }

      this.log('debug', 'Waiting for element', { target, _selector, mode: this.config.mode });
      await page.waitForSelector(selector, {
        state: 'attached',
        timeout: maxWaitMs ?? defaultTimeout,
      });
      return { success: true, target, waitedMs: Date.now() - startTime, found: true };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return {
        success: false,
        target,
        waitedMs: Date.now() - startTime,
        found: false,
        error: message,
      };
    }
  }

  async waitForModal(): Promise<void> {
    const page = this.getPage();
    if (!page) {
      throw new Error('Browser not connected');
    }
    const selector = IRACING_SELECTORS.wizard.modal;
    const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;

    await page.waitForSelector(selector, {
      state: 'attached',
      timeout,
    });
  }

  async waitForStep(stepNumber: number): Promise<void> {
    const page = this.getPage();
    if (!page) {
      throw new Error('Browser not connected');
    }

    if (!this.isRealMode()) {
      await page.evaluate((step) => {
        document.body.setAttribute('data-step', String(step));
      }, stepNumber);
    }

    const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;
    await page.waitForSelector(`[data-step="${stepNumber}"]`, {
      state: 'attached',
      timeout,
    });
  }

  async waitForWizardStep(stepName: keyof typeof IRACING_SELECTORS.wizard.stepContainers): Promise<void> {
    const page = this.getPage();
    if (!page || !this.isRealMode()) return;

    const containerSelector = IRACING_SELECTORS.wizard.stepContainers[stepName];
    if (!containerSelector) {
      this.log('warn', `Unknown wizard step: ${stepName}`);
      return;
    }

    try {
      this.log('debug', `Waiting for wizard step: ${stepName}`, { _selector: containerSelector });
      await page.waitForSelector(containerSelector, {
        state: 'attached',
        timeout: 15000,
      });
      await page.waitForTimeout(100);
    } catch (error) {
      this.log('warn', `Wizard step not attached: ${stepName}`, { error: String(error) });
    }
  }

  async detectCurrentWizardPage(): Promise<string | null> {
    const page = this.getPage();
    if (!page) {
      return null;
    }

    try {
      const containers = IRACING_SELECTORS.wizard.stepContainers;

      for (const [pageName, selector] of Object.entries(containers)) {
        const count = await page.locator(selector).count();
        if (count > 0) {
          this.log('debug', 'Detected wizard page', { pageName, selector });
          return pageName;
        }
      }

      this.log('debug', 'No wizard page detected');
      return null;
    } catch (error) {
      this.log('debug', 'Error detecting wizard page', { error: String(error) });
      return null;
    }
  }

  synchronizeStepCounter(expectedStep: number, actualPage: string | null): number {
    if (!actualPage) {
      return 0;
    }

    let actualStep: number | null = null;
    for (const [step, pageName] of Object.entries(IRacingDomNavigator.STEP_TO_PAGE_MAP)) {
      if (pageName === actualPage) {
        actualStep = parseInt(step, 10);
        break;
      }
    }

    if (actualStep === null) {
      return 0;
    }

    const skipOffset = actualStep - expectedStep;

    if (skipOffset > 0) {
      const skippedSteps: number[] = [];
      for (let i = expectedStep; i < actualStep; i++) {
        skippedSteps.push(i);
      }

      this.log('warn', 'Wizard auto-skip detected', {
        expectedStep,
        actualStep,
        skipOffset,
        skippedSteps,
      });

      return skipOffset;
    }

    return 0;
  }

  async getCurrentStep(): Promise<number | null> {
    const page = this.getPage();
    if (!page) {
      return null;
    }

    if (this.isRealMode()) {
      return null;
    }

    const stepAttr = await page.getAttribute('body', 'data-step');
    return stepAttr ? parseInt(stepAttr, 10) : null;
  }

  private async isWizardModalDismissedInternal(): Promise<boolean> {
    const page = this.getPage();
    if (!page || !this.isRealMode()) {
      return false;
    }

    try {
      const stepContainerSelectors = Object.values(IRACING_SELECTORS.wizard.stepContainers);

      for (const containerSelector of stepContainerSelectors) {
        const count = await page.locator(containerSelector).count();
        if (count > 0) {
          this.log('debug', 'Wizard step container attached, wizard is active', { containerSelector });
          return false;
        }
      }

      const modalSelector = '#create-race-modal, [role="dialog"], .modal.fade';
      const modalExists = (await page.locator(modalSelector).count()) > 0;

      if (!modalExists) {
        this.log('debug', 'No wizard modal element found - dismissed');
        return true;
      }

      this.log('debug', 'Wizard step containers not attached, waiting 1000ms to confirm dismissal vs transition');
      await page.waitForTimeout(1000);

      for (const containerSelector of stepContainerSelectors) {
        const count = await page.locator(containerSelector).count();
        if (count > 0) {
          this.log('debug', 'Wizard step container attached after delay - was just transitioning', {
            containerSelector,
          });
          return false;
        }
      }

      this.log('info', 'No wizard step containers attached after delay - confirmed dismissed by user');
      return true;
    } catch {
      return false;
    }
  }

  async checkWizardDismissed(_currentStep: number): Promise<void> {
    if (!this.isRealMode() || currentStep < 3) {
      return;
    }

    if (await this.isWizardModalDismissedInternal()) {
      this.log('info', 'Race creation wizard was dismissed by user');
      if (this.onWizardDismissed) {
        await this.onWizardDismissed().catch(() => {});
      }
      throw new Error('WIZARD_DISMISSED: User closed the race creation wizard');
    }
  }
}