import { TIMEOUTS } from '../../utils/timeouts';
import { Page, Locator, expect } from '@playwright/test';

/**
 * 企业小程序 H5 URL
 */
const MINI_BASE_URL = process.env.E2E_BASE_URL || 'http://localhost:8080';
const MINI_LOGIN_URL = `${MINI_BASE_URL}/mini`;

/**
 * 企业小程序 Page Object
 *
 * 用于企业小程序 E2E 测试
 * H5 页面路径: /mini
 *
 * 主要功能：
 * - 小程序登录（手机号 + 密码）
 * - Token 管理
 * - 页面导航和验证
 *
 * @example
 * ```typescript
 * const miniPage = new EnterpriseMiniPage(page);
 * await miniPage.goto();
 * await miniPage.login('13800138000', 'password123');
 * await miniPage.expectLoginSuccess();
 * ```
 */
export class EnterpriseMiniPage {
  readonly page: Page;

  // ===== 页面级选择器 =====
  /** 登录页面容器 */
  readonly loginPage: Locator;
  /** 页面标题 */
  readonly pageTitle: Locator;

  // ===== 登录表单选择器 =====
  /** 手机号输入框 */
  readonly phoneInput: Locator;
  /** 密码输入框 */
  readonly passwordInput: Locator;
  /** 登录按钮 */
  readonly loginButton: Locator;

  // ===== 主页选择器（登录后） =====
  /** 用户信息显示区域 */
  readonly userInfo: Locator;
  /** 退出登录按钮 */
  readonly logoutButton: Locator;

  constructor(page: Page) {
    this.page = page;

    // 初始化登录页面选择器
    // Taro 组件在 H5 渲染时会传递 data-testid 到 DOM (使用 taro-view-core 等组件)
    this.loginPage = page.getByTestId('mini-login-page');
    this.pageTitle = page.getByTestId('mini-page-title');

    // 登录表单选择器 - 使用 data-testid
    this.phoneInput = page.getByTestId('mini-phone-input');
    this.passwordInput = page.getByTestId('mini-password-input');
    this.loginButton = page.getByTestId('mini-login-button');

    // 主页选择器（登录后可用）
    this.userInfo = page.getByTestId('mini-user-info');
    // 退出登录按钮 - 使用 getByText 而非 getByRole
    this.logoutButton = page.getByText('退出登录');
  }

  // ===== 导航和基础验证 =====

  /**
   * 移除开发服务器的覆盖层 iframe（防止干扰测试）
   */
  private async removeDevOverlays(): Promise<void> {
    await this.page.evaluate(() => {
      // 移除 react-refresh-overlay 和 webpack-dev-server-client-overlay
      const overlays = document.querySelectorAll('#react-refresh-overlay, #webpack-dev-server-client-overlay');
      overlays.forEach(overlay => overlay.remove());

      // 移除 vConsole 开发者工具覆盖层
      const vConsole = document.querySelector('#__vconsole');
      if (vConsole) {
        vConsole.remove();
      }
    });
  }

  /**
   * 导航到企业小程序 H5 登录页面
   */
  async goto(): Promise<void> {
    await this.page.goto(MINI_LOGIN_URL);
    // 移除开发服务器的覆盖层
    await this.removeDevOverlays();
    // 使用 auto-waiting 机制，等待页面容器可见
    await this.expectToBeVisible();
  }

  /**
   * 验证登录页面关键元素可见
   */
  async expectToBeVisible(): Promise<void> {
    // 等待页面容器可见
    await this.loginPage.waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
    // 验证页面标题
    await this.pageTitle.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
  }

  // ===== 登录功能方法 =====

  /**
   * 填写手机号
   * @param phone 手机号（11位数字）
   *
   * 注意：使用 click + type 方法触发自然的用户输入事件
   * Taro Input 组件需要完整的事件流才能正确更新 react-hook-form 状态
   */
  async fillPhone(phone: string): Promise<void> {
    // 先移除覆盖层，确保输入可操作
    await this.removeDevOverlays();
    // 点击聚焦，然后清空（使用 Ctrl+A + Backspace 模拟用户操作）
    await this.phoneInput.click();
    // 等待元素聚焦
    await this.page.waitForTimeout(100);
    // 使用 type 方法输入，会自动覆盖现有内容
    await this.phoneInput.type(phone, { delay: 50 });
    // 等待表单验证更新
    await this.page.waitForTimeout(200);
  }

  /**
   * 填写密码
   * @param password 密码（6-20位）
   *
   * 注意：使用 click + type 方法触发自然的用户输入事件
   * Taro Input 组件需要完整的事件流才能正确更新 react-hook-form 状态
   */
  async fillPassword(password: string): Promise<void> {
    // 先移除覆盖层，确保输入可操作
    await this.removeDevOverlays();
    // 点击聚焦
    await this.passwordInput.click();
    // 等待元素聚焦
    await this.page.waitForTimeout(100);
    // 使用 type 方法输入
    await this.passwordInput.type(password, { delay: 50 });
    // 等待表单验证更新
    await this.page.waitForTimeout(200);
  }

  /**
   * 点击登录按钮
   */
  async clickLoginButton(): Promise<void> {
    // 使用 force: true 避免被开发服务器的覆盖层阻止
    await this.loginButton.click({ force: true });
  }

  /**
   * 执行登录操作（完整流程）
   * @param phone 手机号
   * @param password 密码
   */
  async login(phone: string, password: string): Promise<void> {
    await this.fillPhone(phone);
    await this.fillPassword(password);
    await this.clickLoginButton();
  }

  /**
   * 验证登录成功
   *
   * 登录成功后应该跳转到主页或显示用户信息
   */
  async expectLoginSuccess(): Promise<void> {
    // 使用 auto-waiting 机制，等待 URL 变化或用户信息显示
    // 小程序登录成功后会跳转到 dashboard 页面

    // 等待 URL 变化，使用 Promise.race 实现超时
    await this.page.waitForURL(
      url => url.pathname.includes('/dashboard') || url.pathname.includes('/pages/yongren/dashboard'),
      { timeout: TIMEOUTS.PAGE_LOAD }
    ).catch(() => {
      // 如果没有跳转，检查是否显示用户信息
      // 注意：此验证将在 Story 12.5 E2E 测试中完全实现
      // 当前仅提供基础结构
    });
  }

  /**
   * 验证登录失败（错误提示显示）
   * @param expectedErrorMessage 预期的错误消息（可选）
   */
  async expectLoginError(expectedErrorMessage?: string): Promise<void> {
    // 等待一下，让后端响应或前端验证生效
    await this.page.waitForTimeout(1000);

    // 验证仍然在登录页面（未跳转）
    const currentUrl = this.page.url();
    expect(currentUrl).toContain('/mini');

    // 验证登录页面容器仍然可见
    await expect(this.loginPage).toBeVisible();

    // 如果提供了预期的错误消息，尝试验证
    if (expectedErrorMessage) {
      // 尝试查找错误消息（可能在 Toast、Modal 或表单验证中）
      const errorElement = this.page.getByText(expectedErrorMessage, { exact: false }).first();
      await errorElement.isVisible().catch(() => false);
      // 不强制要求错误消息可见，因为后端可能不会返回错误
    }
  }

  // ===== Token 管理方法 =====

  /**
   * 获取当前存储的 token
   * @returns token 字符串，如果不存在则返回 null
   *
   * 注意：Taro.getStorageSync 在 H5 环境下映射到 localStorage
   * token 直接存储为字符串，不是 JSON 格式
   *
   * Taro H5 可能使用以下键名格式：
   * - 直接键名: 'enterprise_token'
   * - 带前缀: 'taro_app_storage_key'
   * - 或者其他变体
   */
  async getToken(): Promise<string | null> {
    const result = await this.page.evaluate(() => {
      // 获取所有 localStorage 键
      const keys = Object.keys(localStorage);
      const storage: Record<string, string> = {};
      keys.forEach(k => storage[k] = localStorage.getItem(k) || '');

      // 尝试各种可能的键名
      // 1. 直接键名
      const token = localStorage.getItem('enterprise_token');
      if (token) return token;

      // 2. 带前缀的键名（Taro 可能使用前缀）
      const prefixedKeys = keys.filter(k => k.includes('token') || k.includes('auth'));
      for (const key of prefixedKeys) {
        const value = localStorage.getItem(key);
        if (value && value.length > 20) { // JWT token 通常很长
          return value;
        }
      }

      // 3. 其他常见键名
      return (
        localStorage.getItem('token') ||
        localStorage.getItem('auth_token') ||
        sessionStorage.getItem('token') ||
        sessionStorage.getItem('auth_token') ||
        null
      );
    });
    return result;
  }

  /**
   * 设置 token（用于测试前置条件）
   * @param token token 字符串
   */
  async setToken(token: string): Promise<void> {
    await this.page.evaluate((t) => {
      localStorage.setItem('token', t);
      localStorage.setItem('auth_token', t);
    }, token);
  }

  /**
   * 清除所有认证相关的存储
   */
  async clearAuth(): Promise<void> {
    await this.page.evaluate(() => {
      // 清除企业小程序相关的认证数据
      localStorage.removeItem('enterprise_token');
      localStorage.removeItem('enterpriseUserInfo');

      // 清除其他常见 token 键
      localStorage.removeItem('token');
      localStorage.removeItem('auth_token');
      sessionStorage.removeItem('token');
      sessionStorage.removeItem('auth_token');
    });
  }

  // ===== 主页元素验证方法 =====

  /**
   * 验证主页元素可见（登录后）
   * 根据实际小程序主页结构调整
   */
  async expectHomePageVisible(): Promise<void> {
    // 使用 auto-waiting 机制，等待主页元素可见
    // 注意：此方法将在 Story 12.5 E2E 测试中使用，当前仅提供基础结构
    // 根据实际小程序主页的 data-testid 调整
    const dashboard = this.page.getByTestId('mini-dashboard');
    await dashboard.waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
  }

  /**
   * 获取用户信息显示的文本
   * @returns 用户信息文本
   */
  async getUserInfoText(): Promise<string | null> {
    const userInfo = this.userInfo;
    const count = await userInfo.count();
    if (count === 0) {
      return null;
    }
    return await userInfo.textContent();
  }

  // ===== 退出登录方法 =====

  /**
   * 退出登录
   */
  async logout(): Promise<void> {
    // 点击退出登录按钮
    await this.logoutButton.click();
    // 等待页面加载完成
    await this.page.waitForLoadState('domcontentloaded');
  }

  /**
   * 验证已退出登录（返回登录页面）
   */
  async expectLoggedOut(): Promise<void> {
    // 验证返回到登录页面
    await this.loginPage.waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
  }
}
