"use strict";

/**
 * 抖音视频下载 — Puppeteer 版（参考 zhenqianba Electron DouyinVideoDownloader）
 * 直链走 HTTP；分享链接用浏览器拦截 aweme detail API 后下载。
 */

const fs = require("fs");
const path = require("path");
const https = require("https");
const http = require("http");

const { DouyinBrowserManager, getRuntimeDataPath } = require("./douyin_browser_manager.js");

const DIRECT_VIDEO_PATTERN =
  /https?:\/\/[^\s"'<>]+?\.(mp4|mov|m4v|webm|avi|mkv)(\?[^\s"'<>]*)?/i;

/** 视频页导航超时（毫秒），可通过 AICLIENT_DOUYIN_PAGE_TIMEOUT_MS 覆盖 */
const PAGE_NAV_TIMEOUT_MS =
  Number(process.env.AICLIENT_DOUYIN_PAGE_TIMEOUT_MS) || 60_000;
/** 等待 aweme detail API 拦截的超时（毫秒） */
const API_INTERCEPT_TIMEOUT_MS =
  Number(process.env.AICLIENT_DOUYIN_API_WAIT_MS) || 120_000;
/** 打开视频页后额外等待，便于接口与播放器初始化 */
const PAGE_SETTLE_MS =
  Number(process.env.AICLIENT_DOUYIN_PAGE_SETTLE_MS) || 5_000;

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

class DouyinVideoDownloader {
  constructor(externalBrowser) {
    this.externalBrowser = externalBrowser || null;
    this.browserManager = DouyinBrowserManager.getInstance();
    this.userDataPath = getRuntimeDataPath("browser-data", "douyin");
  }

  getDownloadDir() {
    const downloadDir = getRuntimeDataPath("downloads", "videos");
    if (!fs.existsSync(downloadDir)) {
      fs.mkdirSync(downloadDir, { recursive: true });
    }
    return downloadDir;
  }

  inferVideoExtension(videoUrl) {
    try {
      const pathname = new URL(videoUrl).pathname.toLowerCase();
      const matchedExt = pathname.match(/\.(mp4|mov|m4v|webm|avi|mkv)$/i);
      if (matchedExt) return `.${matchedExt[1].toLowerCase()}`;
    } catch (e) {
      console.warn("inferVideoExtension failed:", e.message);
    }
    return ".mp4";
  }

  isDirectVideoUrl(videoUrl) {
    return DIRECT_VIDEO_PATTERN.test(String(videoUrl || "").trim());
  }

  downloadRemoteFile(fileUrl, savePath, headers, redirectCount = 0) {
    return new Promise((resolve, reject) => {
      if (redirectCount > 5) {
        reject(new Error("Too many redirects while downloading video"));
        return;
      }
      const client = fileUrl.startsWith("https://") ? https : http;
      const fileStream = fs.createWriteStream(savePath);
      let settled = false;
      const fail = (error) => {
        if (settled) return;
        settled = true;
        fileStream.destroy();
        if (fs.existsSync(savePath)) fs.unlinkSync(savePath);
        reject(error);
      };
      const request = client.get(fileUrl, { headers }, (response) => {
        const statusCode = response.statusCode ?? 0;
        const location = response.headers.location;
        if (statusCode >= 300 && statusCode < 400 && location) {
          fileStream.close();
          if (fs.existsSync(savePath)) fs.unlinkSync(savePath);
          const redirectUrl = location.startsWith("http")
            ? location
            : new URL(location, fileUrl).toString();
          this.downloadRemoteFile(redirectUrl, savePath, headers, redirectCount + 1)
            .then(resolve)
            .catch(reject);
          return;
        }
        if (statusCode !== 200) {
          fail(new Error(`HTTP ${statusCode}: ${response.statusMessage}`));
          return;
        }
        response.pipe(fileStream);
        fileStream.on("finish", () => {
          if (settled) return;
          settled = true;
          fileStream.close();
          resolve();
        });
      });
      request.on("error", fail);
      fileStream.on("error", (err) => {
        request.destroy();
        fail(err);
      });
      request.setTimeout(120000, () => {
        request.destroy();
        fail(new Error("Download timed out after 120 seconds"));
      });
    });
  }

  async initBrowser() {
    if (this.externalBrowser) {
      console.log("使用外部提供的浏览器实例");
      return;
    }
    await this.browserManager.initBrowser();
  }

  async extractAwemeIdAsync(url) {
    let normalizedUrl = String(url || "").trim();
    if (!normalizedUrl.startsWith("http")) {
      normalizedUrl = "https://" + normalizedUrl;
    }
    const patterns = [
      /\/video\/(\d+)/,
      /modal_id=(\d+)/,
      /share\/video\/(\d+)/,
      /item_id=(\d+)/,
      /aweme_id=(\d+)/,
    ];
    for (const pattern of patterns) {
      const match = normalizedUrl.match(pattern);
      if (match) return match[1];
    }
    if (
      normalizedUrl.includes("v.douyin.com") ||
      normalizedUrl.includes("iesdouyin.com")
    ) {
      try {
        const realUrl = await this.resolveShortUrl(normalizedUrl);
        if (realUrl && realUrl !== normalizedUrl) {
          for (const pattern of patterns) {
            const match = realUrl.match(pattern);
            if (match) return match[1];
          }
        }
      } catch (e) {
        console.error("解析短链接失败:", e.message);
      }
    }
    return null;
  }

  resolveShortUrl(shortUrl) {
    return new Promise((resolve, reject) => {
      const url = new URL(shortUrl);
      const options = {
        hostname: url.hostname,
        path: url.pathname + url.search,
        method: "HEAD",
        headers: {
          "User-Agent":
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        },
        timeout: 30000,
      };
      const req = https.request(options, (res) => {
        if (
          res.statusCode &&
          res.statusCode >= 300 &&
          res.statusCode < 400 &&
          res.headers.location
        ) {
          resolve(res.headers.location);
        } else {
          resolve(shortUrl);
        }
      });
      req.on("error", reject);
      req.on("timeout", () => {
        req.destroy();
        reject(new Error("短链接解析超时"));
      });
      req.end();
    });
  }

  interceptVideoInfo(page, awemeId) {
    return new Promise((resolve) => {
      let found = false;
      const handler = async (response) => {
        if (found) return;
        const url = response.url();
        if (
          url.includes("aweme/v1/web/aweme/detail") &&
          url.includes(awemeId)
        ) {
          try {
            const text = await response.text();
            let data;
            try {
              data = JSON.parse(text);
            } catch {
              const jsonStr = text.replace(/^[^{]*/, "");
              data = JSON.parse(jsonStr);
            }
            if (data.aweme_detail) {
              found = true;
              page.off("response", handler);
              resolve(data.aweme_detail);
            }
          } catch (error) {
            console.error("解析 API 响应失败:", error.message);
          }
        }
      };
      page.on("response", handler);
      setTimeout(() => {
        if (!found) {
          page.off("response", handler);
          console.log("API 拦截超时，将尝试其他方法");
          resolve(null);
        }
      }, API_INTERCEPT_TIMEOUT_MS);
    });
  }

  async downloadVideo(videoUrl, savePath) {
    const headers = {
      "User-Agent":
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
      Referer: "https://www.douyin.com/",
      Accept: "*/*",
      "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    };
    return this.downloadRemoteFile(videoUrl, savePath, headers);
  }

  async downloadDirectVideo(videoUrl) {
    try {
      if (!this.isDirectVideoUrl(videoUrl)) {
        return { success: false, error: "Unsupported direct video url" };
      }
      const downloadDir = this.getDownloadDir();
      const extension = this.inferVideoExtension(videoUrl);
      const videoPath = path.join(downloadDir, `direct_${Date.now()}${extension}`);
      const headers = {
        "User-Agent":
          "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        Accept: "*/*",
        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
      };
      await this.downloadRemoteFile(videoUrl, videoPath, headers);
      return {
        success: true,
        videoPath,
        audioPath: videoPath.replace(path.extname(videoPath), "_audio.wav"),
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error),
      };
    }
  }

  async downloadDouyinVideo(videoUrl) {
    let page = null;
    try {
      console.log("开始下载抖音视频:", videoUrl);
      const awemeId = await this.extractAwemeIdAsync(videoUrl);
      if (!awemeId) {
        return { success: false, error: "无法从URL中提取视频ID，请检查链接格式" };
      }
      console.log("提取到视频ID:", awemeId);

      const downloadDir = this.getDownloadDir();
      const videoPath = path.join(
        downloadDir,
        `douyin_${awemeId}_${Date.now()}.mp4`
      );

      await this.initBrowser();
      if (this.externalBrowser) {
        page = await this.externalBrowser.newPage();
      } else {
        page = await this.browserManager.newPage();
      }

      const videoInfoPromise = this.interceptVideoInfo(page, awemeId);
      const directVideoUrl = `https://www.douyin.com/video/${awemeId}`;
      console.log("访问视频页面:", directVideoUrl);

      try {
        await page.goto(directVideoUrl, {
          waitUntil: "domcontentloaded",
          timeout: PAGE_NAV_TIMEOUT_MS,
        });
        await sleep(PAGE_SETTLE_MS);
        const currentUrl = page.url();
        const isLoginPage =
          currentUrl.includes("/login") || currentUrl.includes("login?");
        if (isLoginPage) {
          console.log("页面跳转到登录页，尝试引导登录...");
          await this.ensureLoggedIn(page);
          await page.goto(directVideoUrl, {
            waitUntil: "domcontentloaded",
            timeout: PAGE_NAV_TIMEOUT_MS,
          });
          await sleep(PAGE_SETTLE_MS);
          if (page.url().includes("/login")) {
            return {
              success: false,
              error:
                "无法访问视频页面，请先扫码登录抖音（设置 AICLIENT_PUPPETEER_HEADLESS=0 显示浏览器窗口）",
            };
          }
        }
      } catch (gotoError) {
        console.log(
          "页面 goto 超时（已忽略），继续等待 API:",
          gotoError.message
        );
      }

      const videoInfo = await videoInfoPromise;
      if (!videoInfo) {
        try {
          const pageContent = await page.content();
          if (pageContent.includes("aweme_detail")) {
            return {
              success: true,
              videoPath,
              videoInfo: {
                aweme_id: awemeId,
                desc: `抖音视频 ${awemeId}`,
              },
              audioPath: videoPath.replace(".mp4", "_audio.wav"),
            };
          }
        } catch (pageError) {
          console.error("页面解析失败:", pageError.message);
        }
        return {
          success: false,
          error: "无法获取视频信息，可能视频不存在或访问受限",
        };
      }

      const videoUrls = videoInfo.video?.play_addr?.url_list;
      if (!videoUrls || !videoUrls.length) {
        return { success: false, error: "无法获取视频下载链接，可能视频版权限制" };
      }

      const downloadUrl = videoUrls[0];
      console.log("开始下载视频文件...");
      await this.downloadVideo(downloadUrl, videoPath);
      console.log("视频下载完成:", videoPath, "size:", fs.statSync(videoPath).size);

      return {
        success: true,
        videoPath,
        videoInfo,
        audioPath: videoPath.replace(".mp4", "_audio.wav"),
      };
    } catch (error) {
      console.error("下载视频失败:", error);
      let errorMessage = error instanceof Error ? error.message : String(error);
      if (
        errorMessage.includes("3221225781") ||
        errorMessage.includes("0xC0000135")
      ) {
        errorMessage =
          "浏览器启动失败（错误码: 0xC0000135）。请安装 VC++ 运行库: https://aka.ms/vs/17/release/vc_redist.x64.exe";
      } else if (/puppeteer|browser|Could not find Chrome/i.test(errorMessage)) {
        errorMessage =
          "浏览器启动失败。请在 src-tauri/resources/nodejs 执行 npm install，或设置 AICLIENT_CHROMIUM_PATH";
      } else if (/timeout/i.test(errorMessage)) {
        errorMessage = "网络超时，请检查网络连接或重试";
      }
      return { success: false, error: errorMessage };
    } finally {
      if (page) {
        try {
          await page.close();
        } catch (closeError) {
          console.log("关闭页面失败:", closeError.message);
        }
      }
    }
  }

  async ensureLoggedIn(page) {
    try {
      if (this.externalBrowser) {
        console.log("使用外部浏览器，跳过登录检查");
        return;
      }
      const loginCookieNames = [
        "sessionid",
        "uid_tt",
        "LOGIN_STATUS",
        "sid_guard",
        "sid_tt",
      ];
      let cookies = await page.cookies("https://www.douyin.com");
      let loginCookies = cookies.filter(
        (c) => loginCookieNames.includes(c.name) && c.value
      );
      if (loginCookies.length > 0) {
        console.log("检测到已登录 Cookie");
        return;
      }

      await page.goto("https://www.douyin.com/", {
        waitUntil: "domcontentloaded",
        timeout: PAGE_NAV_TIMEOUT_MS,
      });
      await sleep(PAGE_SETTLE_MS);

      const isLoggedIn = await page.evaluate(() => {
        const loginBtnSelectors = [
          '[data-e2e="login-button"]',
          ".login-btn",
          'button[class*="login"]',
        ];
        const userInfoSelectors = [
          '[data-e2e="user-info"]',
          '[data-e2e="user-avatar"]',
          '[class*="userInfo"]',
          ".user-avatar",
          ".nickname",
        ];
        const hasLoginButton = loginBtnSelectors.some((s) =>
          document.querySelector(s)
        );
        const hasUserInfo = userInfoSelectors.some((s) =>
          document.querySelector(s)
        );
        return hasUserInfo || !hasLoginButton;
      });

      if (isLoggedIn) {
        console.log("用户已登录");
        return;
      }

      const headless =
        process.env.AICLIENT_PUPPETEER_HEADLESS !== "0" &&
        process.env.AICLIENT_PUPPETEER_HEADLESS !== "false";
      if (headless) {
        console.log(
          "无头模式下无法扫码登录。请设置环境变量 AICLIENT_PUPPETEER_HEADLESS=0 后重试"
        );
        return;
      }

      console.log("=".repeat(60));
      console.log("请在浏览器窗口中使用抖音 APP 扫码登录");
      console.log("=".repeat(60));

      const loginTimeout = 120000;
      const checkInterval = 3000;
      const startTime = Date.now();
      while (Date.now() - startTime < loginTimeout) {
        await sleep(checkInterval);
        cookies = await page.cookies("https://www.douyin.com");
        loginCookies = cookies.filter(
          (c) => loginCookieNames.includes(c.name) && c.value
        );
        if (loginCookies.length > 0) {
          console.log("登录成功");
          break;
        }
      }
    } catch (error) {
      console.error("登录引导失败:", error.message);
    }
  }

  async close() {
    if (this.externalBrowser) return;
    try {
      await this.browserManager.close();
    } catch (error) {
      console.error("关闭浏览器失败:", error.message);
    }
  }
}

DouyinVideoDownloader.DIRECT_VIDEO_PATTERN = DIRECT_VIDEO_PATTERN;

module.exports = { DouyinVideoDownloader };
