import { Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { Quest } from '../types';

/**
 * Quests Page Object
 * Handles all interactions on the /quests route
 *
 * Real DOM structure (QuestsPage.tsx + QuestForm.tsx + QuestList.tsx):
 *   Button "🗂️ Propose a Quest"   — opens QuestForm overlay
 *   QuestForm overlay:
 *     #title input
 *     #description textarea
 *     #quest_type select
 *     #priority select
 *     #status select
 *     #location select (required)
 *     button "Propose Quest" / "Revise Quest"
 *   QuestList:
 *     [data-testid="quest-item"][data-quest-title="…"]
 *       button "✓ Complete Quest"
 *       button "✏️ Revise"
 *       button "✕ Abandon"
 *   Filter buttons (plain <button> elements):
 *     Status: "All Quests", "In Progress", "Completed", "Blocked"
 *     Type:   "All Types", "The Journey", "The Battle", "The Fellowship", "The Ring"
 *     Location: per-location buttons
 *   QuestCompletionModal:
 *     h2 "⚔️ Quest Challenge"
 */
export class QuestsPage extends BasePage {
  /** Title of the quest targeted by the last clickQuest() call */
  private selectedQuestTitle: string | null = null;

  // --- Form (inside quest-form-overlay) ---
  private readonly formOverlay = () => this.page.locator('.quest-form-overlay').first();
  private readonly questTitleInput = () => this.formOverlay().locator('#title').first();
  private readonly questDescriptionInput = () => this.formOverlay().locator('#description').first();
  private readonly questTypeSelect = () => this.formOverlay().locator('#quest_type').first();
  private readonly questPrioritySelect = () => this.formOverlay().locator('#priority').first();
  private readonly questStatusSelect = () => this.formOverlay().locator('#status').first();
  private readonly questLocationSelect = () => this.formOverlay().locator('#location').first();
  private readonly submitQuestButton = () =>
    this.formOverlay().locator('button[type="submit"]').first();

  // --- Quest list items ---
  private readonly questItems = () => this.page.locator('[data-testid="quest-item"]');

  // Returns the card element for a given quest title
  private questCard(title: string) {
    return this.page.locator(`[data-testid="quest-item"][data-quest-title="${title}"]`).first();
  }

  // --- Completion mini-game modal ---
  private readonly miniGameModal = () =>
    this.page.locator('h2:has-text("Quest Challenge")').locator('../../..').first();

  async goto(): Promise<void> {
    // Don't use page.goto() as it remounts the React app and loses session state
    // Instead, use internal React Router navigation via page.click() on the nav link
    const currentUrl = this.page.url();
    if (currentUrl.includes('/quests')) {
      // Already on quests page - navigate away and back to force component remount and data refresh
      const dashboardLink = this.page.locator('a[href="/dashboard"]').first();
      if (await dashboardLink.count().then(c => c > 0)) {
        await dashboardLink.click();
        await this.page.waitForURL('**/dashboard', { timeout: 5000 });
        await this.page.waitForTimeout(500);
      }
    }
    const questsLink = this.page.locator('a[href="/quests"]').first();
    const linkExists = await questsLink.count().then(c => c > 0);
    
    if (linkExists) {
      await questsLink.click();
      // Wait for navigation to complete
      await this.page.waitForURL('**/quests', { timeout: 10000 });
      await this.page.waitForTimeout(1000); // Wait for quest data to load
    } else {
      // Fallback to page.goto() if no link found
      await super.goto('/quests');
    }
  }

  /**
   * Wait for the quests page to be fully loaded
   */
  async isLoaded(): Promise<boolean> {
    try {
      // Wait for either the "Propose a Quest" button or at least one quest item
      await Promise.race([
        this.page
          .getByRole('button', { name: /propose a quest/i })
          .waitFor({ state: 'visible', timeout: 5000 }),
        this.page
          .locator('[data-testid="quest-item"]')
          .first()
          .waitFor({ state: 'visible', timeout: 5000 }),
      ]);
      return true;
    } catch (e) {
      return false;
    }
  }

  /**
   * Open the quest creation form
   */
  private async openCreateForm(): Promise<void> {
    await this.page.getByRole('button', { name: /propose a quest/i }).first().click();
    await this.formOverlay().waitFor({ state: 'visible', timeout: 10000 });
  }

  /**
   * Create a new quest.
   * Automatically picks the first available location and priority when not provided.
   */
  async createQuest(quest: Omit<Quest, 'id'>): Promise<void> {
    await this.openCreateForm();
    await this.questTitleInput().fill(quest.title);
    await this.questDescriptionInput().fill(quest.description);

    if (quest.type) {
      await this.questTypeSelect().selectOption({ label: quest.type });
    }

    // priority — map test values to form values, or pick first non-empty option
    if (quest.priority) {
      const priorityMap: Record<string, string> = {
        Low: 'Standard',
        Medium: 'Important',
        High: 'Critical',
        Critical: 'Critical',
        Important: 'Important',
        Standard: 'Standard',
      };
      const formPriority = priorityMap[quest.priority] || quest.priority;
      await this.questPrioritySelect().selectOption({ label: formPriority });
    } else {
      // Pick first non-empty priority option
      await this.questPrioritySelect().selectOption({ index: 1 });
    }

    // Location is required — pick first available if not specified
    if (quest.location) {
      await this.questLocationSelect().selectOption({ label: new RegExp(quest.location, 'i') });
    } else {
      await this.questLocationSelect().selectOption({ index: 1 });
    }

    // Dark magic checkbox
    if (quest.darkMagic) {
      const checkbox = this.formOverlay().locator('#is_dark_magic');
      if (!(await checkbox.isChecked())) {
        await checkbox.click();
      }
    }

    await this.submitQuestButton().click();
    // Wait for form to close
    await this.formOverlay().waitFor({ state: 'hidden', timeout: 10000 }).catch(() => {});
  }

  /**
   * Get all quest titles currently visible in the list
   */
  async getQuestTitles(): Promise<string[]> {
    try {
      await this.pollUntil(
        async () => (await this.questItems().count()) > 0,
        10000,
        500,
      );
    } catch {
      return [];
    }

    const items = this.questItems();
    const count = await items.count();
    const titles: string[] = [];
    for (let i = 0; i < count; i++) {
      const attr = await items.nth(i).getAttribute('data-quest-title');
      if (attr) {
        titles.push(attr.trim());
      } else {
        const text = await items.nth(i).locator('h3').first().textContent();
        if (text) titles.push(text.trim().replace(/^[^\w]*/, '').trim());
      }
    }
    return titles;
  }

  /**
   * Select a quest to operate on.
   * Stores the title for subsequent editQuest() / deleteQuest() calls.
   */
  async clickQuest(questTitle: string): Promise<void> {
    this.selectedQuestTitle = questTitle;
    // Scroll the card into view
    try {
      await this.questCard(questTitle).scrollIntoViewIfNeeded();
    } catch {
      // Card may not have data-quest-title yet — fall back
    }
  }

  /**
   * Filter quests by status using the status-filter buttons.
   * Accepts both display values ("It Is Done") and API values ("it_is_done").
   */
  async filterByStatus(status: string): Promise<void> {
    const labelMap: Record<string, RegExp> = {
      'It Is Done': /^Completed/i,
      'it_is_done': /^Completed/i,
      'The Road Goes Ever On...': /^In Progress/i,
      'the_road_goes_ever_on': /^In Progress/i,
      'The Shadow Falls': /^Blocked/i,
      'the_shadow_falls': /^Blocked/i,
      'Not Yet Begun': /^All Quests/i,
      'not_yet_begun': /^All Quests/i,
    };
    const pattern = labelMap[status] || new RegExp(status, 'i');
    // Status filters are expanded by default, so just click
    await this.page.getByRole('button', { name: pattern }).first().click();
    await this.page.waitForTimeout(300);
  }

  /**
   * Filter quests by type using the type-filter buttons.
   * First expands the Quest Type category if it's collapsed.
   */
  async filterByType(type: string): Promise<void> {
    // Expand Type filter category if not already expanded
    try {
      const typeHeader = this.page.getByRole('button', { name: /^Quest Type$/i }).first();
      // Scroll into view first
      await typeHeader.scrollIntoViewIfNeeded();
      const isExpanded = await typeHeader.getAttribute('aria-expanded');
      if (isExpanded === 'false') {
        await typeHeader.click();
        await this.page.waitForTimeout(1500); // Give time for filters to appear and expand
      }
    } catch (e) {
      console.warn('Could not find/expand Quest Type header:', e);
    }
    
    // Wait a bit to ensure DOM is updated
    await this.page.waitForTimeout(500);
    
    // Click the type filter button
    try {
      // Use getByText which is more reliable than has-text
      const button = this.page.getByText(type, { exact: false }).filter({ role: 'button' }).first();
      await button.scrollIntoViewIfNeeded();
      await button.click({ timeout: 15000 });
      await this.page.waitForTimeout(300);
    } catch (e) {
      throw new Error(`Could not click filter button for type "${type}". Error: ${e}`);
    }
  }

  /**
   * Filter quests by location using the location-filter buttons.
   * First expands the Location category if it's collapsed.
   */
  async filterByLocation(locationName: string): Promise<void> {
    // Expand Location filter category if not already expanded
    try {
      const locationHeader = this.page.getByRole('button', { name: /^Location$/i }).first();
      // Scroll into view first
      await locationHeader.scrollIntoViewIfNeeded();
      const isExpanded = await locationHeader.getAttribute('aria-expanded');
      if (isExpanded === 'false') {
        await locationHeader.click();
        await this.page.waitForTimeout(1500); // Give time for filters to appear and expand
      }
    } catch (e) {
      console.warn('Could not find/expand Location header:', e);
    }
    
    // Wait a bit to ensure DOM is updated
    await this.page.waitForTimeout(500);
    
    // Click the location filter button
    try {
      // Use getByText which is more reliable
      const button = this.page.getByText(locationName, { exact: false }).filter({ role: 'button' }).first();
      await button.scrollIntoViewIfNeeded();
      await button.click({ timeout: 15000 });
      await this.page.waitForTimeout(300);
    } catch (e) {
      throw new Error(`Could not click filter button for location "${locationName}". Error: ${e}`);
    }
  }

  /**
   * Open the edit form for the selected quest and apply updates.
   * Must be called after clickQuest().
   */
  async editQuest(updates: Partial<Quest>): Promise<void> {
    const title = this.selectedQuestTitle;
    if (!title) throw new Error('No quest selected. Call clickQuest() first.');

    // Click the "✏️ Revise" button on the quest card
    await this.questCard(title).getByRole('button', { name: /revise/i }).click();
    await this.formOverlay().waitFor({ state: 'visible', timeout: 10000 });

    if (updates.title) {
      await this.questTitleInput().clear();
      await this.questTitleInput().fill(updates.title);
    }
    if (updates.description) {
      await this.questDescriptionInput().clear();
      await this.questDescriptionInput().fill(updates.description);
    }
    if (updates.status) {
      // Map display status to form option label
      const statusMap: Record<string, string> = {
        'Not Yet Begun': 'Not Yet Begun',
        'not_yet_begun': 'Not Yet Begun',
        'The Road Goes Ever On...': 'The Road Goes Ever On...',
        'the_road_goes_ever_on': 'The Road Goes Ever On...',
        'It Is Done': 'It Is Done',
        'it_is_done': 'It Is Done',
        'The Shadow Falls': 'The Shadow Falls',
        'the_shadow_falls': 'The Shadow Falls',
      };
      const label = statusMap[updates.status] || updates.status;
      await this.questStatusSelect().selectOption({ label });
    }

    await this.submitQuestButton().click();
    await this.formOverlay().waitFor({ state: 'hidden', timeout: 10000 }).catch(() => {});
  }

  /**
   * Delete the currently selected quest.
   * Must be called after clickQuest().
   * Handles the native window.confirm dialog automatically.
   */
  async deleteQuest(): Promise<void> {
    const title = this.selectedQuestTitle;
    if (!title) throw new Error('No quest selected. Call clickQuest() first.');

    // Accept the confirm dialog before the click triggers it
    this.page.once('dialog', (dialog) => dialog.accept());
    await this.questCard(title).getByRole('button', { name: /abandon/i }).click();

    // Wait for the card to disappear
    await this.questCard(title).waitFor({ state: 'detached', timeout: 10000 }).catch(() => {});
    this.selectedQuestTitle = null;
  }

  /**
   * Trigger quest completion (opens mini-game modal).
   * Must be called after clickQuest().
   */
  async completeQuest(): Promise<void> {
    const title = this.selectedQuestTitle;
    if (!title) throw new Error('No quest selected. Call clickQuest() first.');

    await this.questCard(title).getByRole('button', { name: /complete quest/i }).click();
    await this.page
      .locator('h2:has-text("Quest Challenge")')
      .waitFor({ state: 'visible', timeout: 10000 });
  }

  /**
   * Check if the mini-game modal is visible
   */
  async isMiniGameModalVisible(): Promise<boolean> {
    return this.page
      .locator('h2:has-text("Quest Challenge")')
      .isVisible({ timeout: 5000 })
      .catch(() => false);
  }

  /**
   * Wait for the mini-game modal to close (hidden or detached)
   */
  async waitForMiniGameModalToClose(): Promise<void> {
    await this.page
      .locator('h2:has-text("Quest Challenge")')
      .waitFor({ state: 'hidden', timeout: 10000 })
      .catch(() => {});
  }

  /**
   * Get the mini-game type from the visible mini-game modal
   */
  async getMiniGameType(): Promise<string> {
    // Each mini-game renders a recognisable heading
    const candidates = ['Trivia', 'Memory', 'Reaction', 'Find the Ring'];
    
    // First try: look for game name as heading text within the modal
    for (const game of candidates) {
      try {
        const heading = this.page.locator(`h2:has-text("${game}"), h3:has-text("${game}")`);
        const count = await heading.count();
        if (count > 0) return game;
      } catch {
        // Continue to next candidate
      }
    }
    
    // Second try: general text search
    for (const game of candidates) {
      try {
        const element = this.page.locator(`text/${game}/i`);
        const visible = await element.isVisible({ timeout: 2000 }).catch(() => false);
        if (visible) return game;
      } catch {
        // Continue to next candidate
      }
    }
    
    return '';
  }

  /**
   * Fail the mini-game by clicking the hidden force-fail trigger button.
   * Uses page.evaluate() to bypass viewport restrictions for the off-screen button.
   */
  async failMiniGame(): Promise<void> {
    // Use page.evaluate to click the hidden test button (bypasses viewport check)
    await this.page.evaluate(() => {
      const btn = document.querySelector('[data-testid="force-game-fail"]') as HTMLElement | null;
      if (btn) btn.click();
    });
    // Wait for the "Try Again" button to appear (fail state shows retry button)
    await this.page.locator('button:has-text("Try Again")').waitFor({ state: 'visible', timeout: 10000 }).catch(() => {});
  }

  /**
   * Win the mini-game by clicking the hidden force-win trigger button.
   * Then click "Claim Reward" to close the modal and mark quest complete.
   */
  async winMiniGame(): Promise<void> {
    // Use page.evaluate to click the hidden test button (bypasses viewport check)
    await this.page.evaluate(() => {
      const btn = document.querySelector('[data-testid="force-game-win"]') as HTMLElement | null;
      if (btn) btn.click();
    });
    // Wait for the Claim Reward button to appear (win state)
    const claimButton = this.page.getByRole('button', { name: /Claim Reward/i }).first();
    await claimButton.waitFor({ state: 'visible', timeout: 10000 });
    await claimButton.click();
    // Wait for quest completion API call and modal to close
    await this.page.locator('h2:has-text("Quest Challenge")').waitFor({ state: 'hidden', timeout: 15000 }).catch(() => {});
  }
}

