from __future__ import annotations

import argparse
import json
import os
import shutil
import subprocess
import time
from pathlib import Path
from typing import Any

import yaml


NPX_EXECUTABLE = shutil.which("npx.cmd") or shutil.which("npx") or "npx"
PLAYWRIGHT_CMD = [NPX_EXECUTABLE, "--yes", "--package", "@playwright/cli", "playwright-cli"]
SESSION_NAME = "vip-report-monthly-sales"
DOWNLOAD_CENTER_URL = "https://compass.vip.com/frontend/index.html#/dataCapturing/downloadCenter"
DEFAULT_DOWNLOAD_DIR = r"C:\vipdata"


def run_cmd(args: list[str], *, cwd: Path, timeout: int = 300) -> subprocess.CompletedProcess[str]:
    env = os.environ.copy()
    npm_cache = cwd / ".npm-cache"
    npm_cache.mkdir(parents=True, exist_ok=True)
    env["NPM_CONFIG_CACHE"] = str(npm_cache)
    return subprocess.run(
        args,
        cwd=str(cwd),
        text=True,
        encoding="utf-8",
        errors="replace",
        capture_output=True,
        timeout=timeout,
        check=True,
        env=env,
    )


def run_playwright(args: list[str], *, cwd: Path, timeout: int = 300) -> str:
    return run_cmd(PLAYWRIGHT_CMD + args, cwd=cwd, timeout=timeout).stdout


def write_js(workdir: Path, name: str, content: str) -> Path:
    p = workdir / name
    p.write_text(content, encoding="utf-8")
    return p


def run_code(session: str, script_path: Path, *, cwd: Path, timeout: int = 420) -> str:
    return run_playwright(["--session", session, "run-code", "--filename", str(script_path)], cwd=cwd, timeout=timeout)


def build_js(spec: dict[str, Any]) -> str:
    payload = json.dumps(spec, ensure_ascii=False)
    return f"""async function(page) {{
  const spec = {payload};
  const sleep = (ms) => page.waitForTimeout(ms);
  const safeName = (name) => String(name || '')
    .replace(/[\\\\/:*?"<>|]/g, '_')
    .replace(/\\s+/g, '_')
    .replace(/_+/g, '_');

  await page.goto(spec.url, {{ waitUntil: 'domcontentloaded', timeout: 120000 }});
  await sleep(3500);

  const rowsSeen = [];
  for (let attempt = 0; attempt < spec.max_retries; attempt++) {{
    const queryBtn = page.locator('button:has-text("查询"), span:has-text("查询"), button:has-text("鏌ヨ"), span:has-text("鏌ヨ")').first();
    if (await queryBtn.count().catch(() => 0)) {{
      await queryBtn.click({{ timeout: 2500 }}).catch(() => null);
    }}
    await sleep(2200);

    const rows = await page.locator('tbody tr').count().catch(() => 0);
    for (let i = 0; i < Math.min(rows, spec.scan_rows); i++) {{
      const row = page.locator('tbody tr').nth(i);
      const text = (await row.innerText().catch(() => '')).replace(/\\s+/g, ' ').trim();
      const nameEl = row.locator('.status-name-span').first();
      let fileName = await nameEl.getAttribute('title').catch(() => '');
      if (!fileName) fileName = (await nameEl.innerText().catch(() => '')).trim();
      if (!fileName) fileName = text;
      rowsSeen.push({{ index: i + 1, fileName, text }});

      const hasKeyword = spec.keywords.every((k) => String(fileName || text).includes(k));
      const isSuccess = /执行成功|成功|鎵ц鎴愬姛|鎴愬姛/.test(text);
      if (!hasKeyword || !isSuccess) continue;

      const downloadBtn = row.locator('button:has-text("下载"), span:has-text("下载"), button:has-text("涓嬭浇"), span:has-text("涓嬭浇")').first();
      if (!(await downloadBtn.count().catch(() => 0))) continue;
      await page.locator('.el-loading-mask').waitFor({{ state: 'hidden', timeout: 10000 }}).catch(() => null);

      const [dl] = await Promise.all([
        page.waitForEvent('download', {{ timeout: 120000 }}),
        downloadBtn.click({{ timeout: 10000, force: true }}),
      ]);
      const suggested = dl.suggestedFilename();
      const finalName = safeName(`${{spec.prefix}}_${{fileName || suggested}}`);
      const targetPath = `${{spec.download_dir.replace(/\\\\/g, '/')}}/${{finalName}}`;
      await dl.saveAs(targetPath);
      return {{ ok: true, savedAs: targetPath, fileName, suggestedFilename: suggested, rowText: text }};
    }}
  }}

  return {{ ok: false, code: 'READY_FILE_NOT_FOUND', keywords: spec.keywords, rowsSeen: rowsSeen.slice(0, 20), url: page.url() }};
}}
"""


def parse_args() -> argparse.Namespace:
    p = argparse.ArgumentParser(description="Download an already-ready Compass download-center file.")
    p.add_argument("--config", default=r"D:\projects\skills\vip-report\config.yaml")
    p.add_argument("--session", default=SESSION_NAME)
    p.add_argument("--download-dir", default=DEFAULT_DOWNLOAD_DIR)
    p.add_argument("--prefix", required=True)
    p.add_argument("--keyword", action="append", default=[])
    p.add_argument("--max-retries", type=int, default=5)
    p.add_argument("--scan-rows", type=int, default=10)
    return p.parse_args()


def main() -> None:
    args = parse_args()
    config_path = Path(args.config).resolve()
    cfg = yaml.safe_load(config_path.read_text(encoding="utf-8"))
    workdir = Path(cfg["paths"]["workdir"]).resolve()
    download_dir = Path(args.download_dir).resolve()
    download_dir.mkdir(parents=True, exist_ok=True)

    js_path = write_js(
        workdir,
        f"tmp-download-ready-{args.prefix}-{time.time_ns()}.js",
        build_js(
            {
                "url": DOWNLOAD_CENTER_URL,
                "download_dir": str(download_dir),
                "prefix": args.prefix,
                "keywords": args.keyword,
                "max_retries": args.max_retries,
                "scan_rows": args.scan_rows,
            }
        ),
    )
    try:
        out = run_code(args.session, js_path, cwd=workdir, timeout=420)
    finally:
        js_path.unlink(missing_ok=True)

    if "### Error" in out:
        raise RuntimeError(out.strip())

    json_line = ""
    for line in out.splitlines():
        s = line.strip()
        if s.startswith("{") and s.endswith("}"):
            json_line = s
            break
    if not json_line:
        raise RuntimeError(f"No JSON result from run-code: {out}")
    result = json.loads(json_line)
    if not result.get("ok", False):
        raise RuntimeError(json.dumps(result, ensure_ascii=False))
    print(json.dumps(result, ensure_ascii=False))


if __name__ == "__main__":
    main()
