/**
 * BrowserWindowManager — 单例浏览器窗口管理器
 *
 * 提供网页加载、截图、JS 执行、内容提取、交互操作能力。
 * 单例懒加载，首次调用时创建，browserClose() 显式销毁。
 *
 * @see docs/Agent网络工具通用设计-v2.md — 第 4 章 browser 浏览器设计
 */

import { BrowserWindow, session } from 'electron';
import log from 'electron-log';

/** Agent 浏览器专用 session partition — 与主应用 default session 完全隔离 */
const AGENT_PARTITION = 'persist:metona-agent-browser';

export interface BrowserOpenOptions {
  url: string;
  waitSelector?: string;
}

export interface BrowserOpenResult {
  title: string;
  url: string;
}

export interface ScreenshotOptions {
  fullPage?: boolean;
  selector?: string;
}

export interface ScreenshotResult {
  data: string; // Base64 PNG
  width: number;
  height: number;
}

export interface ExtractResult {
  text: string;
  links: Array<{ text: string; url: string }>;
}

export interface ScrollOptions {
  direction?: 'down' | 'up' | 'top' | 'bottom';
  selector?: string;
}

export interface WaitOptions {
  selector?: string;
  timeMs?: number;
}

export class BrowserWindowManager {
  private win: BrowserWindow | null = null;
  private currentUrl: string | null = null;
  // v0.3.0 修复: 并发互斥锁 — 串行化所有 open 调用，避免竞态导致窗口状态混乱
  private openChain: Promise<unknown> = Promise.resolve();

  /** 窗口是否就绪 */
  get ready(): boolean {
    return this.win !== null && !this.win.isDestroyed();
  }

  // ===== browserOpen =====

  async open(options: BrowserOpenOptions): Promise<BrowserOpenResult> {
    // v0.3.0 修复: 并发互斥锁 — 串行化所有 open 调用，避免竞态导致窗口状态混乱
    const run = async (): Promise<BrowserOpenResult> => {
      // 若已有窗口加载了不同 URL → 先关闭重建
      if (this.win && this.currentUrl !== options.url) {
        this.destroy();
      }

      if (!this.win) {
        this.win = new BrowserWindow({
          width: 1280,
          height: 800,
          show: false,
          webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            sandbox: true,
            // v0.3.0 修复: 独立 partition，与主应用 default session 完全隔离
            // 防止 Agent 浏览产生的 Cookie/缓存/存储污染主应用
            partition: AGENT_PARTITION,
            // v0.3.0 修复: 恢复 webSecurity，CORS 需求通过 session.webRequest 处理
            webSecurity: true,
            plugins: false,
            webviewTag: false,
          },
        });

        // v0.3.0 修复: 拦截 window.open，Agent 浏览的页面不允许再开新窗口
        this.win.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));

        // v0.3.0 修复: 使用 CORS 放行替代 webSecurity: false
        // 仅对 agent session 放行 CORS，不影响主应用
        const agentSession = session.fromPartition(AGENT_PARTITION);
        agentSession.webRequest.onHeadersReceived((details, callback) => {
          callback({
            responseHeaders: {
              ...details.responseHeaders,
              'Access-Control-Allow-Origin': ['*'],
            },
          });
        });
      }

      await this.loadURLWithTimeout(this.win, options.url, 30_000);
      this.currentUrl = options.url;

      // 可选等待选择器
      if (options.waitSelector) {
        await this.waitForSelector(options.waitSelector, 10_000);
      }

      const title = await this.evaluate('document.title');
      return { title: String(title ?? ''), url: options.url };
    };

    // 串行化：等待前一个 open 完成
    this.openChain = this.openChain.then(run, run);
    return this.openChain as Promise<BrowserOpenResult>;
  }

  // ===== browserScreenshot =====

  async screenshot(options: ScreenshotOptions = {}): Promise<ScreenshotResult> {
    this.ensureReady();
    const win = this.win!;

    // 元素截图
    if (options.selector) {
      const rect = await win.webContents.executeJavaScript(
        `(() => {
          const el = document.querySelector(${JSON.stringify(options.selector)});
          if (!el) return null;
          const r = el.getBoundingClientRect();
          return { x: r.x, y: r.y, width: r.width, height: r.height };
        })()`,
        true,
      ) as { x: number; y: number; width: number; height: number } | null;

      if (!rect) throw new Error(`Element not found: ${options.selector}`);

      const image = await win.webContents.capturePage({
        x: Math.round(rect.x),
        y: Math.round(rect.y),
        width: Math.round(rect.width),
        height: Math.round(rect.height),
      });
      return { data: image.toPNG().toString('base64'), width: rect.width, height: rect.height };
    }

    // 全页截图
    if (options.fullPage) {
      const dims = await win.webContents.executeJavaScript(
        `({ width: document.documentElement.scrollWidth, height: document.documentElement.scrollHeight })`,
        true,
      ) as { width: number; height: number };

      // 先滚动到底部触发懒加载
      await win.webContents.executeJavaScript(
        `window.scrollTo(0, document.body.scrollHeight)`,
        true,
      );
      await this.sleep(500);
      await win.webContents.executeJavaScript(
        `window.scrollTo(0, 0)`,
        true,
      );
      await this.sleep(300);

      const image = await win.webContents.capturePage({
        x: 0, y: 0,
        width: dims.width,
        height: dims.height,
      });
      return { data: image.toPNG().toString('base64'), width: dims.width, height: dims.height };
    }

    // 视口截图
    const image = await win.webContents.capturePage();
    const size = win.getContentSize();
    return { data: image.toPNG().toString('base64'), width: size[0], height: size[1] };
  }

  // ===== browserEvaluate =====

  /**
   * 在浏览器页面上下文中执行 JS
   *
   * #46 修复: 增强安全防护
   * - 审计日志：记录所有 executeJavaScript 调用，便于事后追溯恶意操作
   * - 超时控制：防止恶意 JS 无限阻塞主进程（Electron 原生不支持超时，用 Promise.race 模拟）
   *
   * 注意：完全沙箱隔离较难实现（需要 iframe/Web Worker + API 白名单），
   * 当前先实现审计 + 超时作为缓解措施。webPreferences 已配置 contextIsolation: true
   * 和 sandbox: true，Electron API 不会被暴露给页面。
   */
  async evaluate(js: string): Promise<unknown> {
    this.ensureReady();

    // #46 修复: 审计日志 — 记录所有 executeJavaScript 调用（截断前 500 字符），便于追溯
    log.info('[BrowserWindowManager] evaluate JS (first 500 chars):', js.substring(0, 500));

    // #46 修复: 超时控制 — 防止恶意 JS 无限阻塞主进程
    const EVAL_TIMEOUT_MS = 10_000;
    let timer: ReturnType<typeof setTimeout> | undefined;
    try {
      const result = await Promise.race([
        this.win!.webContents.executeJavaScript(js, true),
        new Promise<never>((_, reject) => {
          timer = setTimeout(() => {
            // 审查修复 M17: 超时后中止页面 JS 执行。
            // executeJavaScript 返回的 Promise 无法取消，页面脚本仍会继续运行，
            // 调用 webContents.stop() 中止页面正在执行的脚本（win 可能已销毁，try/catch 兜底）。
            try { this.win?.webContents.stop(); } catch {}
            reject(new Error(`evaluate timed out after ${EVAL_TIMEOUT_MS}ms`));
          }, EVAL_TIMEOUT_MS);
        }),
      ]);
      if (typeof result === 'string') return result;
      return result;
    } finally {
      if (timer) clearTimeout(timer);
    }
  }

  // ===== browserExtract =====

  async extract(selector?: string): Promise<ExtractResult> {
    this.ensureReady();

    const result = await this.win!.webContents.executeJavaScript(
      `(() => {
        const root = ${selector ? `document.querySelector(${JSON.stringify(selector)})` : 'document.body'};
        if (!root) return null;
        const clone = root.cloneNode(true);
        clone.querySelectorAll('script, style, noscript, iframe, svg, nav, header, footer, aside').forEach(el => el.remove());
        const text = (clone.innerText || '').trim();

        const links = [];
        const anchors = (selector ? root : document).querySelectorAll('a[href^="http"]');
        for (const a of anchors) {
          if (links.length >= 50) break;
          const text = (a.textContent || '').trim();
          if (text && a.href) links.push({ text, url: a.href });
        }
        return { text, links };
      })()`,
      true,
    ) as { text: string; links: Array<{ text: string; url: string }> } | null;

    if (!result) throw new Error(selector ? `Element not found: ${selector}` : 'No body content');

    return { text: result.text, links: result.links };
  }

  // ===== browserClick =====

  async click(selector: string, wait = false): Promise<void> {
    this.ensureReady();

    if (wait) {
      await this.waitForSelector(selector, 10_000);
    }

    const found = await this.win!.webContents.executeJavaScript(
      `(() => {
        const el = document.querySelector(${JSON.stringify(selector)});
        if (!el) return false;
        el.scrollIntoView({ behavior: 'smooth', block: 'center' });
        return true;
      })()`,
      true,
    );

    if (!found) throw new Error(`Element not found: ${selector}`);

    await this.sleep(300);
    await this.win!.webContents.executeJavaScript(
      `(() => {
        const el = document.querySelector(${JSON.stringify(selector)});
        if (!el) return;
        el.click();
      })()`,
      true,
    );
    await this.sleep(500);
  }

  // ===== browserType =====

  async type(selector: string, text: string, options: { clear?: boolean; submit?: boolean } = {}): Promise<void> {
    this.ensureReady();

    const found = await this.win!.webContents.executeJavaScript(
      `(() => {
        const el = document.querySelector(${JSON.stringify(selector)});
        if (!el) return false;
        el.scrollIntoView({ behavior: 'smooth', block: 'center' });
        el.focus();
        return true;
      })()`,
      true,
    );

    if (!found) throw new Error(`Element not found: ${selector}`);
    await this.sleep(300);

    // 清空 + 赋值 + 触发事件（兼容 React/Vue）
    await this.win!.webContents.executeJavaScript(
      `(() => {
        const el = document.querySelector(${JSON.stringify(selector)});
        if (!el) return;
        ${options.clear ? 'el.value = "";' : ''}
        el.value += ${JSON.stringify(text)};
        el.dispatchEvent(new Event('input', { bubbles: true }));
        el.dispatchEvent(new Event('change', { bubbles: true }));
      })()`,
      true,
    );

    if (options.submit) {
      await this.win!.webContents.executeJavaScript(
        `(() => {
          const el = document.querySelector(${JSON.stringify(selector)});
          if (!el) return;
          if (el.form) el.form.submit();
          else el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
        })()`,
        true,
      );
      await this.sleep(1_000);
    } else {
      await this.sleep(300);
    }
  }

  // ===== browserScroll =====

  async scroll(options: ScrollOptions = {}): Promise<void> {
    this.ensureReady();

    if (options.selector) {
      await this.win!.webContents.executeJavaScript(
        `(() => {
          const el = document.querySelector(${JSON.stringify(options.selector)});
          if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
        })()`,
        true,
      );
      return;
    }

    const direction = options.direction ?? 'down';
    const js = {
      down: 'window.scrollBy(0, 500)',
      up: 'window.scrollBy(0, -500)',
      top: 'window.scrollTo(0, 0)',
      bottom: 'window.scrollTo(0, document.body.scrollHeight)',
    }[direction] ?? 'window.scrollBy(0, 500)';

    await this.win!.webContents.executeJavaScript(js, true);
    await this.sleep(300);
  }

  // ===== browserWait =====

  async wait(options: WaitOptions = {}): Promise<void> {
    if (options.selector) {
      await this.waitForSelector(options.selector, options.timeMs ?? 10_000);
    } else {
      await this.sleep(options.timeMs ?? 1_000);
    }
  }

  // ===== browserClose =====

  close(): void {
    this.destroy();
  }

  // ===== 内部辅助 =====

  private ensureReady(): void {
    if (!this.ready) {
      throw new Error('Browser window not ready. Call browser_open first.');
    }
  }

  /** 带超时的 loadURL（Electron 原生不支持 timeout 选项） */
  private async loadURLWithTimeout(win: BrowserWindow, url: string, timeoutMs: number): Promise<void> {
    let timer: NodeJS.Timeout | null = null;
    const timeoutPromise = new Promise<never>((_, reject) => {
      timer = setTimeout(() => reject(new Error(`Page load timeout after ${timeoutMs}ms: ${url}`)), timeoutMs);
    });

    try {
      await Promise.race([
        win.loadURL(url),
        timeoutPromise,
      ]);
    } catch (e) {
      // v0.3.0 修复: 超时后停止页面加载，避免后台继续消耗网络和 CPU 资源
      if (!win.isDestroyed()) {
        try { win.webContents.stop(); } catch { /* ignore */ }
      }
      throw e;
    } finally {
      if (timer) clearTimeout(timer);
    }
  }

  private async waitForSelector(selector: string, timeoutMs: number): Promise<void> {
    const startTime = Date.now();
    while (Date.now() - startTime < timeoutMs) {
      const found = await this.win!.webContents.executeJavaScript(
        `!!document.querySelector(${JSON.stringify(selector)})`,
        true,
      );
      if (found) return;
      await this.sleep(300);
    }
    throw new Error(`Timeout waiting for selector: ${selector}`);
  }

  private destroy(): void {
    if (this.win && !this.win.isDestroyed()) {
      try { this.win.webContents.stop(); } catch { /* ignore */ }
      try { this.win.destroy(); } catch { /* ignore */ }
    }
    this.win = null;
    this.currentUrl = null;
    // v0.3.0 修复: 清理 agent session 存储，防止下一次浏览残留上一次的 Cookie/缓存/localStorage
    try {
      const ses = session.fromPartition(AGENT_PARTITION);
      ses.clearStorageData({
        storages: ['cookies', 'localstorage', 'indexdb', 'shadercache', 'serviceworkers', 'cachestorage'],
      }).catch(() => { /* ignore */ });
      ses.clearCache().catch(() => { /* ignore */ });
    } catch { /* ignore */ }
  }

  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  /** 应用退出时清理 */
  static cleanup(manager: BrowserWindowManager | null): void {
    if (manager) manager.close();
    log.info('[BrowserWindowManager] Cleaned up');
  }
}
