import os
import re
import time
import glob
from dataclasses import dataclass
from typing import Optional

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import TimeoutException
from webdriver_manager.chrome import ChromeDriverManager


# ──────────────────────────────────────────────
#  操作速度控制
# ──────────────────────────────────────────────

@dataclass
class TimingConfig:
    operation_interval: float = 1  # 每个独立操作间隔
    page_load_wait: float = 2  # 页面加载等待
    login_wait: float = 2  # 登录等待
    file_select_wait: float = 2  # 文件选择等待
    element_timeout: float = 30  # 元素定位超时
    login_timeout: float = 30  # 登录超时
    upload_timeout: float = 120  # 上传超时
    server_process_wait: float = 2  # 服务器处理等待
    retry_additional_wait: float = 2  # 每次重试额外等待
    max_retries: int = 2  # 最大重试次数


# ──────────────────────────────────────────────
#  ChromeDriver 工具
# ──────────────────────────────────────────────

def _can_reach_google(timeout: float = 5) -> bool:
    """快速检测能否访问 Google（ChromeDriver 下载源）"""
    import urllib.request, ssl
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    try:
        r = urllib.request.urlopen("https://www.google.com", timeout=timeout, context=ctx)
        return r.status == 200
    except Exception:
        return False


def _set_proxy(proxy: str):
    os.environ['HTTP_PROXY'] = proxy
    os.environ['HTTPS_PROXY'] = proxy


def _clear_proxy():
    os.environ.pop('HTTP_PROXY', None)
    os.environ.pop('HTTPS_PROXY', None)


def install_chromedriver(proxy: str = "") -> str:
    """
    尝试下载 ChromeDriver：
    1. 先用本机直连
    2. 失败则用指定代理（如果有）
    3. 都不行就报错退出
    """
    # 1) 尝试直连
    _clear_proxy()
    print("🌐 检测本机网络...")
    if _can_reach_google():
        print("✓ 本机可直连，使用直连下载 ChromeDriver")
        path = ChromeDriverManager().install()
        print(f"✓ ChromeDriver 已就绪: {path}")
        return path

    print("✗ 本机无法直连 Google")

    # 2) 尝试代理
    if proxy:
        print(f"🌐 尝试代理 {proxy} ...")
        _set_proxy(proxy)
        if _can_reach_google():
            print("✓ 代理可用，使用代理下载 ChromeDriver")
            path = ChromeDriverManager().install()
            _clear_proxy()
            print(f"✓ ChromeDriver 已就绪: {path}")
            return path
        _clear_proxy()
        print("✗ 代理也无法访问 Google")

    # 3) 全部失败
    print("❌ 网络不可用：本机直连和代理均无法访问 Google，无法下载 ChromeDriver")
    print("   请检查网络连接或代理设置后重试")
    exit(1)


# ──────────────────────────────────────────────
#  核心提交器
# ──────────────────────────────────────────────

class GOT10kAutoSubmitter:
    # 固定常量 —— 一般不需要改
    BASE_URL = "http://got-10k.aitestunion.com"
    ACCOUNT_URL = f"{BASE_URL}/user_account"
    ZIP_FILENAME = "got10k_submit.zip"

    def __init__(
            self,
            driver_path: str,
            base_path: str,
            method_keyword: str,
            username: str = "",
            password: str = "",
            timing: TimingConfig = TimingConfig(),
    ):
        self.driver_path = driver_path
        self.base_path = base_path
        self.method_keyword = method_keyword
        self.username = username
        self.password = password
        self.t = timing

        # 从 method_keyword 派生
        self.submit_url = f"{self.BASE_URL}/submit/{method_keyword}"

        self.driver: Optional[webdriver.Chrome] = None
        self.processed_folders: list[str] = []

    # ── 浏览器管理 ──────────────────────────────

    def _build_driver(self):
        opts = Options()
        opts.add_argument("--no-sandbox")
        opts.add_argument("--disable-dev-shm-usage")
        opts.add_argument("--disable-web-security")
        opts.add_argument("--allow-running-insecure-content")
        opts.add_experimental_option("prefs", {
            "download.default_directory": self.base_path,
            "download.prompt_for_download": False,
            "download.directory_upgrade": True,
            "safebrowsing.enabled": True,
        })
        self.driver = webdriver.Chrome(
            service=Service(self.driver_path), options=opts
        )
        print("✓ Chrome 浏览器已启动")

    def _close_driver(self):
        if self.driver:
            try:
                self.driver.quit()
                print("✓ 浏览器已关闭")
            except Exception as e:
                print(f"关闭浏览器时出错: {e}")

    # ── 登录 ────────────────────────────────────

    def _auto_login(self):
        t = self.t
        print("等待登录页面加载...")
        time.sleep(t.login_wait)

        email_field = WebDriverWait(self.driver, t.element_timeout).until(
            EC.presence_of_element_located((By.NAME, "email"))
        )
        password_field = WebDriverWait(self.driver, t.element_timeout).until(
            EC.presence_of_element_located((By.NAME, "password"))
        )

        print(f"输入邮箱: {self.username}")
        email_field.clear()
        time.sleep(t.operation_interval)
        email_field.send_keys(self.username)
        time.sleep(t.operation_interval)

        print("输入密码...")
        password_field.clear()
        time.sleep(t.operation_interval)
        password_field.send_keys(self.password)
        time.sleep(t.operation_interval)

        try:
            cb = self.driver.find_element(By.NAME, "remember_me")
            if not cb.is_selected():
                cb.click()
                print("✓ 已勾选 Keep me logged in")
                time.sleep(t.operation_interval)
        except Exception as e:
            print(f"⚠️  找不到 Keep me logged in: {e}")

        login_btn = WebDriverWait(self.driver, t.element_timeout).until(
            EC.element_to_be_clickable(
                (By.XPATH, "//input[@type='submit'][@value='Login']")
            )
        )
        print("点击登录按钮...")
        login_btn.click()

        WebDriverWait(self.driver, t.login_timeout).until(
            EC.url_contains(f"submit/{self.method_keyword}")
        )
        time.sleep(t.login_wait)

    def _manual_login(self):
        print("\n" + "=" * 60)
        print("⚠️  请在浏览器中手动登录:")
        print("  1. 输入邮箱和密码")
        print("  2. ✅ 勾选 Keep me logged in")
        print("  3. 点击登录并等待跳转到提交页面")
        print("完成后按 Enter 继续...")
        print("=" * 60)
        input()

    def _verify_login(self) -> bool:
        keyword = f"submit/{self.method_keyword}"
        for attempt in range(3):
            url = self.driver.current_url
            print(f"验证登录 ({attempt + 1}/3): {url}")
            if keyword in url:
                print("✓ 已在提交页面")
                return True
            if "login" not in url.lower():
                self.driver.get(self.submit_url)
                time.sleep(self.t.page_load_wait)
                if keyword in self.driver.current_url:
                    return True
            if attempt < 2:
                input("登录验证失败，请确认后按 Enter...")
        return False

    def login(self, manual: bool = False) -> bool:
        """登录。manual=True 强制手动登录（测试模式用）"""
        self.driver.get(self.submit_url)
        time.sleep(self.t.page_load_wait)

        if "login" in self.driver.current_url.lower():
            if manual:
                self._manual_login()
            elif self.username and self.password:
                try:
                    self._auto_login()
                    print("✓ 自动登录成功")
                except Exception as e:
                    print(f"✗ 自动登录失败: {e}，切换手动登录")
                    self._manual_login()
            else:
                self._manual_login()
        else:
            print("✓ 无需登录")

        return self._verify_login()

    # ── 文件夹扫描 ──────────────────────────────
    def scan_folders(self) -> list[str]:
        def natural_sort_key(s):
            return [int(t) if t.isdigit() else t.lower()
                    for t in re.split(r'(\d+)', s)]

        folders = []
        for name in os.listdir(self.base_path):
            full = os.path.join(self.base_path, name)
            if not os.path.isdir(full):
                continue
            # 末尾是性能值（10~100）说明已处理过
            last_part = name.rsplit("_", 1)[-1]
            try:
                val = float(last_part)
                if 20 <= val <= 100:
                    print(f"⏭️  {name} 已提交过，跳过")
                    continue
            except ValueError:
                pass
            zips = glob.glob(
                os.path.join(full, "**", self.ZIP_FILENAME), recursive=True
            )
            if zips:
                folders.append(name)
            else:
                print(f"⚠️  {name} 中未找到 {self.ZIP_FILENAME}")
        folders.sort(key=natural_sort_key)
        return folders

    # ── 单次提交 ────────────────────────────────

    def _find_zip(self, folder_name: str) -> Optional[str]:
        matches = glob.glob(
            os.path.join(self.base_path, folder_name, "**", self.ZIP_FILENAME),
            recursive=True,
        )
        return matches[0] if matches else None

    def _upload(self, zip_path: str) -> bool:
        t = self.t
        self.driver.get(self.submit_url)
        time.sleep(t.page_load_wait)

        if f"submit/{self.method_keyword}" not in self.driver.current_url:
            if "login" in self.driver.current_url:
                print("❌ 登录状态已失效")
                return False
            self.driver.get(self.submit_url)
            time.sleep(t.page_load_wait)

        file_input = WebDriverWait(self.driver, t.element_timeout).until(
            EC.presence_of_element_located((By.NAME, "zip_submission"))
        )
        file_input.send_keys(zip_path)
        time.sleep(t.file_select_wait)

        upload_btn = WebDriverWait(self.driver, t.element_timeout).until(
            EC.presence_of_element_located(
                (By.XPATH, "//input[@type='submit'][@value='Upload']")
            )
        )
        upload_btn.click()
        time.sleep(t.operation_interval)

        try:
            WebDriverWait(self.driver, t.upload_timeout).until(
                EC.url_contains("user_account")
            )
            return True
        except TimeoutException:
            self.driver.get(self.ACCOUNT_URL)
            time.sleep(t.page_load_wait)
            return "user_account" in self.driver.current_url

    # ── 结果解析 ────────────────────────────────

    def _parse_ao_from_table(self) -> Optional[str]:
        WebDriverWait(self.driver, self.t.element_timeout).until(
            EC.presence_of_element_located((By.TAG_NAME, "table"))
        )
        time.sleep(self.t.operation_interval)

        rows = self.driver.find_elements(By.XPATH, "//table//tr")
        for row in rows[1:]:
            cells = row.find_elements(By.TAG_NAME, "td")
            texts = [c.text.strip() for c in cells]
            if not any(self.method_keyword in t.lower() for t in texts):
                continue
            for val in texts:
                try:
                    v = float(val)
                    if 0.1 <= v <= 1.0 or 10 <= v <= 100:
                        pct = v * 100 if v < 1.0 else v
                        return f"{pct:.1f}"
                except ValueError:
                    continue
        return None

    def get_performance(self) -> Optional[str]:
        t = self.t
        for retry in range(t.max_retries):
            try:
                wait = t.server_process_wait + retry * t.retry_additional_wait
                print(f"等待服务器处理 {wait}s（尝试 {retry + 1}/{t.max_retries}）...")
                time.sleep(wait)
                self.driver.refresh()
                time.sleep(t.operation_interval)

                result = self._parse_ao_from_table()
                if result:
                    return result
                print("未找到有效 AO，稍后重试...")
            except Exception as e:
                print(f"获取结果失败: {e}")
        return None

    # ── 重命名 ──────────────────────────────────

    @staticmethod
    def _rename(base: str, old: str, perf: str) -> bool:
        new = f"{old}_{perf}"
        try:
            os.rename(os.path.join(base, old), os.path.join(base, new))
            print(f"✓ 重命名: {old} -> {new}")
            return True
        except Exception as e:
            print(f"✗ 重命名失败: {e}")
            return False

    # ── 单文件夹流程 ────────────────────────────

    def process_folder(self, name: str) -> bool:
        print(f"\n{'─' * 50}")
        print(f"处理: {name}")

        zip_path = self._find_zip(name)
        if not zip_path:
            print(f"✗ 未找到 {self.ZIP_FILENAME}")
            return False

        # 1) 上传
        print(f"[1/3] 上传 {zip_path}")
        if not self._upload(zip_path):
            print("✗ 上传失败")
            return False
        print("✓ 上传成功")
        time.sleep(self.t.operation_interval)

        # 2) 获取结果
        print("[2/3] 获取性能结果")
        perf = self.get_performance()
        if not perf:
            manual = input(f"手动输入 {name} 的 AO（留空跳过）: ").strip()
            if manual:
                try:
                    float(manual)
                    perf = manual
                except ValueError:
                    print("✗ 无效数字")
                    return False
            else:
                return False
        print(f"✓ AO = {perf}")

        # 3) 重命名
        print("[3/3] 重命名文件夹")
        if self._rename(self.base_path, name, perf):
            self.processed_folders.append(f"{name}_{perf}")
            return True
        return False

    # ── 主运行循环 ──────────────────────────────

    def run(self, folders: list[str], mode: str = "submit"):
        """
        mode="test":   手动登录 → 只提交第一个 → 确认无误后可选继续批处理
        mode="submit": 自动登录 → 全部批处理
        """
        try:
            self._build_driver()

            manual_login = (mode == "test")
            if not self.login(manual=manual_login):
                print("❌ 登录失败")
                return

            ok, fail, skip = 0, 0, 0

            if mode == "test":
                # ── 测试模式：先跑第一个 ──
                first = folders[0]
                print(f"\n🧪 测试模式：先提交第一个文件夹 [{first}]")
                if self.process_folder(first):
                    ok += 1
                    print(f"✅ {first} 完成")
                else:
                    fail += 1
                    print(f"❌ {first} 失败")

                if len(folders) <= 1:
                    # 只有一个，直接结束
                    pass
                else:
                    print(f"\n还剩 {len(folders) - 1} 个文件夹待处理")
                    ch = input("确认结果无误，继续批处理剩余？(y/n): ").strip().lower()
                    if ch == 'y':
                        for i, folder in enumerate(folders[1:], 2):
                            print(f"\n📁 [{i}/{len(folders)}] {folder}")
                            if self.process_folder(folder):
                                ok += 1
                                print(f"✅ {folder} 完成")
                            else:
                                fail += 1
                                print(f"❌ {folder} 失败")
                    else:
                        skip = len(folders) - 1
                        print("⏭️ 已跳过剩余文件夹")

            else:
                # ── 提交模式：全部批处理 ──
                for i, folder in enumerate(folders, 1):
                    print(f"\n📁 [{i}/{len(folders)}] {folder}")
                    if self.process_folder(folder):
                        ok += 1
                        print(f"✅ {folder} 完成")
                    else:
                        fail += 1
                        print(f"❌ {folder} 失败")

            print(f"\n{'=' * 50}")
            print(f"✅ 成功: {ok}  ❌ 失败: {fail}  ⏭️ 跳过: {skip}")
            if self.processed_folders:
                print(f"已处理: {self.processed_folders}")

        except KeyboardInterrupt:
            print("\n⚠️ 用户中断")
        except Exception as e:
            print(f"\n❌ 严重错误: {e}")
        finally:
            self._close_driver()


# ══════════════════════════════════════════════
#  入口
# ══════════════════════════════════════════════

if __name__ == "__main__":

    # ── 运行模式 ──
    mode = "submit"  # "test"=手动登录，只提交第一个，确认后再批处理；"submit"=自动登录，直接全部批处理

    # ── 代理（留空 "" 则只用本机直连；填写后会在直连失败时自动尝试代理） ──
    # 下载google chrome driver需要本机开启代理，或手动指定代理，会自动检测
    proxy = "http://127.0.0.1:10808"

    # ── 核心参数 ──
    base_path = r"C:\path\to\your\results"  # 包含 ep[xx] 文件夹的根目录
    method_keyword = "dino3"  # GOT-10k 上注册的方法名，也用于拼接提交 URL

    username = "your_email@example.com"  # GOT-10k 登录邮箱（留空则手动登录）
    password = "your_password"  # GOT-10k 登录密码（留空则手动登录）

    # ── 操作速度控制（用默认值就不用改） ──
    timing = TimingConfig(
        operation_interval=1,  # 通用操作间隔（秒），如点击按钮后的短暂停顿
        page_load_wait=2,  # 页面导航后等待加载的时间（秒）
        login_wait=2,  # 登录页面加载 / 登录完成后的等待（秒）
        file_select_wait=2,  # 选择文件后等待浏览器识别的时间（秒）
        element_timeout=30,  # 等待页面元素出现的最大超时（秒）
        login_timeout=30,  # 等待登录跳转完成的最大超时（秒）
        upload_timeout=120,  # 等待文件上传并跳转到结果页的最大超时（秒）
        server_process_wait=2,  # 上传后等待服务器处理结果的基础时间（秒）
        retry_additional_wait=2,  # 每次重试额外增加的等待时间（秒）
        max_retries=2,  # 获取性能结果的最大重试次数
    )

    # ── 构建提交器 ──
    submitter = GOT10kAutoSubmitter(
        driver_path="", base_path=base_path, method_keyword=method_keyword,
        username=username, password=password, timing=timing,
    )

    # ── 预检查：先扫描文件夹，没有就不启动浏览器 ──
    folders = submitter.scan_folders()
    print(f"提交目录：{base_path}")
    if not folders:
        print("❌ 没有找到待处理的文件夹，退出")
        exit()
    print(f"📊 找到 {len(folders)} 个待处理文件夹: {folders}")
    print(f"🔧 运行模式: {mode}")

    # ── 安装 ChromeDriver 并启动 ──
    submitter.driver_path = install_chromedriver(proxy)
    submitter.run(folders, mode=mode)

