import { expect, Page, Locator } from "@playwright/test";

// Get test user data based on the ID.
// '01' -> user 111
// otherwise -> user 000
// (... which is a weird convention but I haven't taken the time to change it)
export function getTestUserData(id?: string): {
  seedPhrase: string;
  userName: string;
  did: string;
} {
  switch (id) {
    case "01":
      return {
        seedPhrase:
          "island fever beef wine urban aim vacant quit afford total poem flame service calm better adult neither color gaze forum month sister imitate excite",
        userName: "User One",
        did: "did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39",
      };
    default: // to user 00
      return {
        seedPhrase:
          "rigid shrug mobile smart veteran half all pond toilet brave review universe ship congress found yard skate elite apology jar uniform subway slender luggage",
        userName: "User Zero",
        did: "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F",
      };
  }
}

export async function importUserFromAccount(page: Page, id?: string): Promise<string> {
  // Navigate to AccountViewView to use the Identity Switcher
  await page.goto("./account");

  // Click "Show Advanced Settings" to reveal the identity switcher
  await page.getByTestId("advancedSettings").click();

  // Use the identity switcher to add User Zero
  await page.locator("#switch-identity-link").click();
  await page.locator("#start-link").click();

  // Select "You have a seed" option
  await page.getByText("You have a seed").click();

  // Get User Zero's seed phrase using the new method
  const userZeroData = getTestUserData(id);

  // Enter User Zero's seed phrase
  await page.getByPlaceholder("Seed Phrase").fill(userZeroData.seedPhrase);

  await page.getByRole("button", { name: "Import" }).click();

  // PHASE 1 FIX: Wait for registration status to settle
  // This ensures that components have the correct isRegistered status
  await waitForRegistrationStatusToSettle(page);

  return userZeroData.did;
}

// Import the seed and switch to the user based on the ID.
export async function importUser(page: Page, id?: string): Promise<string> {
  const userData = getTestUserData(id);
  const { seedPhrase, userName, did } = userData;

  // Import ID
  await page.goto("./start");
  await page.getByText("You have a seed").click();
  await page.getByPlaceholder("Seed Phrase").fill(seedPhrase);
  await page.getByRole("button", { name: "Import" }).click();

  // Check DID
  await expect(page.getByRole("code")).toContainText(did);
  // ... and ensure the app retrieves the registration status
  await expect(
    page.locator("#sectionUsageLimits").getByText("Checking")
  ).toBeHidden();
  
  // PHASE 1 FIX: Wait for registration check to complete and update UI elements
  // This ensures that components like InviteOneView have the correct isRegistered status
  await waitForRegistrationStatusToSettle(page);
  
  return did;
}

export async function importUserAndCloseOnboarding(
  page: Page,
  id?: string
): Promise<string> {
  const did = await importUser(page, id);
  await page.goto("./");
  await page.getByTestId("closeOnboardingAndFinish").click();
  return did;
}

// This is to switch to someone already in the identity table. It doesn't include registration.
export async function switchToUser(page: Page, did: string): Promise<void> {
  // This is the direct approach but users have to tap on things so we'll do that instead.

  await page.goto("./account");
  
  // Wait for the page to load and the advanced settings element to be visible
  await page.waitForLoadState('networkidle');
  await page.getByTestId("advancedSettings").waitFor({ state: 'visible' });

  const switchIdentityLink = page.locator("#switch-identity-link");

  if (await switchIdentityLink.isHidden()) {
    await page.getByTestId("advancedSettings").click();
    await switchIdentityLink.click();
  } else {
    await switchIdentityLink.click();
  }

  const didElem = await page.locator(`code:has-text("${did}")`);
  await didElem.isVisible();
  await didElem.click();

  // wait for the switch to happen and the account page to fully load
  await page.getByTestId("didWrapper").locator('code:has-text("did:")');
}

export function createContactName(did: string): string {
  return "User " + did.slice(11, 14);
}

export async function deleteContact(page: Page, did: string): Promise<void> {
  await page.goto("./contacts");
  const contactName = createContactName(did);
  // go to the detail page for this contact
  await page
    .locator(
      `li[data-testid="contactListItem"] h2:has-text("${contactName}") + div svg.fa-circle-info`
    )
    .click();
  // delete the contact
  await page.locator("button > svg.fa-trash-can").click();
  await page.locator('div[role="alert"] button:has-text("Yes")').click();
  // for some reason, .isHidden() (without expect) doesn't work
  await expect(
    page.locator('div[role="alert"] button:has-text("Yes")')
  ).toBeHidden();
}

export async function generateNewEthrUser(page: Page): Promise<string> {
  await page.goto("./start");
  await page.getByTestId("newSeed").click();
  await expect(page.locator('span:has-text("Created")')).toBeVisible();

  await page.goto("./account");
  const didElem = await page
    .getByTestId("didWrapper")
    .locator('code:has-text("did:")');
  const newDid = await didElem.innerText();
  return newDid;
}

// Function to generate a random string of specified length
// Note that this only generates up to 10 characters
export async function generateRandomString(length: number): Promise<string> {
  return Math.random()
    .toString(36) // base 36 only generates up to 10 characters
    .substring(2, 2 + length);
}

// Function to create an array of unique strings
export async function createUniqueStringsArray(
  count: number
): Promise<string[]> {
  const stringsArray: string[] = [];
  const stringLength = 5; // max of 10; see generateRandomString

  for (let i = 0; i < count; i++) {
    let randomString = await generateRandomString(stringLength);
    stringsArray.push(randomString);
  }

  return stringsArray;
}

// Function to create an array of two-digit non-zero numbers
export async function createRandomNumbersArray(
  count: number
): Promise<number[]> {
  const numbersArray: number[] = [];

  for (let i = 0; i < count; i++) {
    let randomNumber = Math.floor(Math.random() * 99) + 1;
    numbersArray.push(randomNumber);
  }

  return numbersArray;
}

export function isLinuxEnvironment() {
  return process.platform === "linux";
}

export function getOSSpecificTimeout(): number {
  // Increase base timeout for Linux
  const isLinux = process.platform === "linux";
  return isLinux ? 180000 : 60000; // 3 minutes for Linux, 1 minute for others
}

export function getOSSpecificConfig() {
  if (isLinuxEnvironment()) {
    return {
      retries: 2,
      timeout: 90000, // Increased global timeout
      expect: {
        timeout: 30000, // Increased expect timeout
      },
      // Add video recording for failed tests on Linux
      use: {
        video: "retain-on-failure",
        trace: "retain-on-failure",
      },
    };
  }
  return {};
}

// Add helper for test grouping
export function isResourceIntensiveTest(testPath: string): boolean {
  return (
    testPath.includes("35-record-gift-from-image-share") ||
    testPath.includes("40-add-contact")
  );
}

// Retry logic for load-sensitive operations
export async function retryOperation<T>(
  operation: () => Promise<T>,
  maxRetries: number = 3,
  baseDelay: number = 1000,
  description: string = 'operation'
): Promise<T> {
  let lastError: Error;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error as Error;
      
      if (attempt === maxRetries) {
        console.log(`❌ ${description} failed after ${maxRetries} attempts`);
        throw error;
      }
      
      // Exponential backoff with jitter
      const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 500;
      console.log(`⚠️ ${description} failed (attempt ${attempt}/${maxRetries}), retrying in ${Math.round(delay)}ms...`);
      
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError!;
}

// Specific retry wrappers for common operations
export async function retryWaitForSelector(
  page: Page,
  selector: string,
  options?: { timeout?: number; state?: 'attached' | 'detached' | 'visible' | 'hidden' }
): Promise<void> {
  const timeout = options?.timeout || getOSSpecificTimeout();
  
  await retryOperation(
    () => page.waitForSelector(selector, { ...options, timeout }),
    3,
    1000,
    `waitForSelector(${selector})`
  );
}

export async function retryWaitForLoadState(
  page: Page,
  state: 'load' | 'domcontentloaded' | 'networkidle',
  options?: { timeout?: number }
): Promise<void> {
  const timeout = options?.timeout || getOSSpecificTimeout();
  
  await retryOperation(
    () => page.waitForLoadState(state, { ...options, timeout }),
    2,
    2000,
    `waitForLoadState(${state})`
  );
}

export async function retryClick(
  page: Page,
  locator: Locator,
  options?: { timeout?: number }
): Promise<void> {
  const timeout = options?.timeout || getOSSpecificTimeout();
  
  await retryOperation(
    async () => {
      await locator.waitFor({ state: 'visible', timeout });
      await locator.click();
    },
    3,
    1000,
    `click(${locator.toString()})`
  );
}

// Adaptive timeout utilities for load-sensitive operations
export function getAdaptiveTimeout(baseTimeout: number, multiplier: number = 1.5): number {
  // Check if we're in a high-load environment
  const isHighLoad = process.env.NODE_ENV === 'test' && 
                    (process.env.CI || process.env.TEST_LOAD_STRESS);
  
  // Check system memory usage (if available)
  const memoryUsage = process.memoryUsage();
  const memoryPressure = memoryUsage.heapUsed / memoryUsage.heapTotal;
  
  // Adjust timeout based on load indicators
  let loadMultiplier = 1.0;
  
  if (isHighLoad) {
    loadMultiplier = 2.0;
  } else if (memoryPressure > 0.8) {
    loadMultiplier = 1.5;
  } else if (memoryPressure > 0.6) {
    loadMultiplier = 1.2;
  }
  
  return Math.floor(baseTimeout * loadMultiplier * multiplier);
}

export function getFirefoxTimeout(baseTimeout: number): number {
  // Firefox typically needs more time, especially under load
  return getAdaptiveTimeout(baseTimeout, 2.0);
}

export function getNetworkIdleTimeout(): number {
  return getAdaptiveTimeout(5000, 1.5);
}

export function getElementWaitTimeout(): number {
  return getAdaptiveTimeout(10000, 1.3);
}

export function getPageLoadTimeout(): number {
  return getAdaptiveTimeout(30000, 1.4);
}

/**
 * PHASE 1 FIX: Wait for registration status to settle
 * 
 * This function addresses the timing issue where:
 * 1. User imports identity → Database shows isRegistered: false
 * 2. HomeView loads → Starts async registration check
 * 3. Other views load → Use cached isRegistered: false
 * 4. Async check completes → Updates database to isRegistered: true
 * 5. But other views don't re-check → Plus buttons don't appear
 * 
 * This function waits for the async registration check to complete
 * without interfering with test navigation.
 */
export async function waitForRegistrationStatusToSettle(page: Page): Promise<void> {
  try {
    // Wait for the initial registration check to complete
    // This is indicated by the "Checking" text disappearing from usage limits
    await expect(
      page.locator("#sectionUsageLimits").getByText("Checking")
    ).toBeHidden({ timeout: 15000 });
    
    // Before navigating back to the page, we'll trigger a registration check
    // by navigating to home and waiting for the registration process to complete

    const currentUrl = page.url();
    
    // Navigate to home to trigger the registration check
    await page.goto('./');
    await page.waitForLoadState('networkidle');
    
    // Wait for the registration check to complete by monitoring the usage limits section
    // This ensures the async registration check has finished
    await page.waitForFunction(() => {
      const usageLimits = document.querySelector('#sectionUsageLimits');
      if (!usageLimits) return true; // No usage limits section, assume ready
      
      // Check if the "Checking..." spinner is gone
      const checkingSpinner = usageLimits.querySelector('.fa-spin');
      if (checkingSpinner) return false; // Still loading
      
      // Check if we have actual content (not just the spinner)
      const hasContent = usageLimits.querySelector('p') || usageLimits.querySelector('button');
      return hasContent !== null; // Has actual content, not just spinner
    }, { timeout: 10000 });
    
    // Also navigate to account page to ensure activeDid is set and usage limits are loaded
    await page.goto('./account');
    await page.waitForLoadState('networkidle');
    
    // Wait for the usage limits section to be visible and loaded
    await page.waitForFunction(() => {
      const usageLimits = document.querySelector('#sectionUsageLimits');
      if (!usageLimits) return false; // Section should exist on account page
      
      // Check if the "Checking..." spinner is gone
      const checkingSpinner = usageLimits.querySelector('.fa-spin');
      if (checkingSpinner) return false; // Still loading
      
      // Check if we have actual content (not just the spinner)
      const hasContent = usageLimits.querySelector('p') || usageLimits.querySelector('button');
      return hasContent !== null; // Has actual content, not just spinner
    }, { timeout: 15000 });
    
    // Navigate back to the original page if it wasn't home
    if (!currentUrl.includes('/')) {
      await page.goto(currentUrl);
      await page.waitForLoadState('networkidle');
    }
    
  } catch (error) {
    // Registration status check timed out, continuing anyway
    // This may indicate the user is not registered or there's a server issue
  }
}
