import sys
import time
import json
import os
import re
import uuid
import socket
import threading
import random
import requests
from datetime import datetime
from typing import Optional, Dict

from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
    QGridLayout, QGroupBox, QLabel, QLineEdit, QSpinBox, QPushButton, 
    QTableWidget, QTableWidgetItem, QAbstractItemView, QTextEdit, 
    QHeaderView, QMessageBox
)
from PyQt5.QtCore import pyqtSignal, QObject, Qt

from DrissionPage.common import Keys
from DrissionPage import ChromiumPage, ChromiumOptions

import configure
from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
from toolkit.vs_cloud_api import VSCloudApi
from toolkit.mihomo_tunnel import MihomoTunnel
from vs_types import BizLogicError 
from utils.mouse import HumanMouse
from utils.keyboard import HumanKeyboard
from utils.fingerprint_utils import FingerprintGenerator
from utils.fake_utils import generate_random_account_detail

def load_proxies(pool_name):
    """从 config/proxies.json 读取对应的代理池"""
    config_path = os.path.join(os.path.dirname(__file__), 'config', 'proxies.json')
    try:
        with open(config_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
            proxies = data.get(pool_name, [])
            if not proxies:
                raise ValueError(f"代理池 '{pool_name}' 为空或不存在！")
            return proxies
    except Exception as e:
        raise Exception(f"读取代理配置文件失败: {e}")

# ================= 用于跨线程更新UI的信号类 =================
class GuiSignals(QObject):
    log_signal = pyqtSignal(str)              # 传递日志文本
    status_signal = pyqtSignal(int, str)      # 传递行号和状态文本

# ================= 核心业务类 =================
class TlsRegistrator:
    def __init__(self, tls_url, proxy_config: Optional[Dict]=None, capsolver_key: Optional[str]=None, signals: GuiSignals=None):
        self.proxy_config = proxy_config
        self.capsolver_key = capsolver_key
        self.account_detail = None  # 动态绑定
        self.signals = signals      # 绑定UI信号
        
        self.instance_id = uuid.uuid4().hex[:8]
        self.tls_url = tls_url
        self.workspace = os.path.abspath(os.path.join("data/temp_browser_data", f"reg_session_{self.instance_id}"))
        self.page = None
        self.mouse = None
        self.keyboard = None
        self.tunnel = None

    def _log(self, msg):
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        log_str = f"[{now}][Bot-{self.instance_id}] {msg}"
        if self.signals:
            self.signals.log_signal.emit(log_str)
        else:
            print(log_str)

    def _get_free_port(self):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.bind(('', 0))
            return s.getsockname()[1]
        
    def save_screenshot(self, name_prefix):
        try:
            timestamp = int(time.time())
            filename = f"{self.instance_id}_{name_prefix}_{timestamp}.jpg"
            save_path = os.path.join("data", filename)
            os.makedirs("data", exist_ok=True)
            self.page.get_screenshot(path=save_path, full_page=False)
            self._log(f"Screenshot saved to {save_path}")
        except Exception as e:
            self._log(f"Failed to save screenshot: {e}")

    def init_browser(self):
        self._log("Initializing browser...")
        co = ChromiumOptions()
        port = self._get_free_port()
        co.set_local_port(port)
        co.set_user_data_path(self.workspace)
        
        chrome_path = configure.CHROME_PATH or os.getenv("CHROME_BIN")
        if chrome_path and os.path.exists(chrome_path):
            co.set_paths(browser_path=chrome_path)
        
        if self.proxy_config and self.proxy_config.get("ip"):
            p = self.proxy_config
            if p.get('username') and p.get('password'):
                self._log(f"Starting Proxy Tunnel for {p.get('ip')}...")
                exit_node = {
                    "name": "ExitNode", "type": p.get('proto'),
                    "server": p.get('ip'), "port": p.get('port'),
                    "username": p.get('username'), "password": p.get('password')
                }
                relay_node = random.choice(configure.MIHOMO_RELAY_NODES) if configure.MIHOMO_RELAY_NODES else None
                mihomo_path = configure.MIHOMO_BIN_PATH or os.getenv("MIHOMO_BIN")
                if not mihomo_path:
                    raise BizLogicError(message='Mihomo path is null')
                self.tunnel = MihomoTunnel(mihomo_path, exit_node=exit_node, relay_node=relay_node)
                local_proxy = self.tunnel.start()
                co.set_argument(f'--proxy-server={local_proxy}')
            else:
                proxy_str = f"{p.get('proto')}://{p.get('ip')}:{p.get('port')}"
                co.set_argument(f'--proxy-server={proxy_str}')
        else:
            self._log("[WARN] No proxy configured!")

        fingerprint_gen = FingerprintGenerator()
        specific_fp = fingerprint_gen.generate(self.instance_id)
        
        co.headless(False)
        co.set_argument('--no-sandbox')
        co.set_argument('--disable-dev-shm-usage')
        co.set_argument('--window-size=1920,1080')
        co.set_argument('--disable-blink-features=AutomationControlled')
        co.set_argument(f"--fingerprint={specific_fp.get('seed')}")
        co.set_argument(f"--fingerprint-platform={specific_fp.get('platform')}")
        co.set_argument(f"--fingerprint-brand={specific_fp.get('brand')}")
        
        self.page = ChromiumPage(co)
        self.page.get(self.tls_url)
        time.sleep(5)
        
        cf_bypasser = CloudflareBypasser(self.page, log=True)
        cf_bypasser.bypass(max_retry=8)
        time.sleep(3)
        cf_bypasser.handle_waiting_room()
        
        self._log("正在初始化拟人化工具...")
        self.mouse = HumanMouse(self.page, debug=False)
        self.keyboard = HumanKeyboard(self.page)
        
        viewport_width = self.page.rect.viewport_size[0]
        viewport_height = self.page.rect.viewport_size[1]
        init_x = random.randint(10, viewport_width - 10)
        init_y = random.randint(10, viewport_height - 10)
        self.mouse.move(init_x, init_y) 
        self._log("浏览器初始化完成。")

    def solve_captcha(self, page_url: str, task_type: str, site_key: str, use_proxy=False, action: str=None, api_domain: str=None) -> str:
        if not self.capsolver_key: raise ValueError("Capsolver API key missing")
        task = {"type": task_type, "websiteURL": page_url, "websiteKey": site_key}
        if api_domain: task["apiDomain"] = api_domain
        if use_proxy:
            proxy = self.proxy_config
            task["proxyType"] = proxy.get('proto', 'http')
            task["proxyAddress"] = proxy.get('ip')
            task["proxyPort"] = int(proxy.get('port'))
            if proxy.get('username'):
                task["proxyLogin"] = proxy.get('username')
                task["proxyPassword"] = proxy.get('password')
        if action: task["pageAction"] = action

        payload = {"clientKey": self.capsolver_key, "task": task}
        res = requests.post("https://api.capsolver.com/createTask", json=payload, timeout=20)
        if res.status_code != 200 or res.json().get("errorId") != 0:
            raise Exception(f"Failed to create capsolver task: {res.text}")
        task_id = res.json().get("taskId")
        
        for _ in range(30):
            r = requests.post("https://api.capsolver.com/getTaskResult", json={"clientKey": self.capsolver_key, "taskId": task_id}, timeout=20)
            data = r.json()
            if data.get("status") == "ready": 
                return data["solution"].get("gRecaptchaResponse") or data["solution"].get("token")
            time.sleep(3)
        raise Exception("Capsolver task timeout")

    def register(self):
        email = self.account_detail.get('email')
        password = self.account_detail.get('pwd')
        self._log(f"开始执行自动注册: {email}")

        btn_selector = '#submit'
        if not self.page.wait.ele_displayed(btn_selector, timeout=3):
            register_btn = self.page.ele("tag:a@@href:registration")
            if register_btn: self.mouse.human_click_ele(register_btn)
            time.sleep(3)
        if not self.page.wait.ele_displayed(btn_selector, timeout=10):
            raise BizLogicError(message=f"Can't find selector={btn_selector}")
            
        time.sleep(random.uniform(0.5, 1))
        self._log("正在填写邮箱和密码...")
        self.mouse.human_click_ele(self.page.ele('#email'))
        self.keyboard.type_text(email, humanize=True)
        time.sleep(random.uniform(0.2, 0.5))
        
        self.mouse.human_click_ele(self.page.ele('#password'))
        self.keyboard.type_text(password, humanize=True)
        time.sleep(random.uniform(0.2, 0.5))
        
        self.mouse.human_click_ele(self.page.ele('#confirm-password'))
        self.keyboard.type_text(password, humanize=True)
        time.sleep(random.uniform(0.2, 0.5))

        self._log("正在勾选必选条款...")
        for checkbox_id in ['#terms-and-conditions', '#biometric-data', '#privacy-notice']:
            self.mouse.human_click_ele(self.page.ele(checkbox_id).next())
            time.sleep(random.uniform(0.3, 0.6))
                
        self._log("提交注册...")
        time.sleep(random.uniform(0.3, 0.6))
        self.mouse.human_click_ele(self.page.ele(btn_selector))

        self._log("正在等待验证结果 (最多15秒)...")
        success_dialog = self.page.wait.ele_displayed('tag:h1@text():Check your email inbox', timeout=15)
        if not success_dialog:
            self.save_screenshot("failed_submit.png") 
            raise BizLogicError(message='Failed to submit account registration')     
        self._log("✅ 操作成功！已弹出提示：Check your email inbox")
        return True
    
    def activate(self, sent_at=None):
        email = self.account_detail.get('email')
        if not sent_at:
            sent_at = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
        
        self._log("获取激活邮件...")
        content_out = VSCloudApi.Instance().fetch_mail_content(
            email='hujiarui8@gmail.com', sender='TLSContact', recipient=email,
            subject_keywords='TLSContact', body_keywords='', sent_date=sent_at, expiry=600
        )
        match = re.search(r'https://\S+', content_out)
        activate_link = match.group(0) if match else None
        if not activate_link:
            raise Exception("未找到激活链接")
            
        self.page.get(activate_link)
        btn_selector = "#activation-pending-button"
        if not self.page.wait.ele_displayed(btn_selector, timeout=10):
            raise BizLogicError(message=f"Wait ele={btn_selector} timeout")
        self.page.ele(btn_selector).click()
        time.sleep(3)
        self._log("✅ 激活成功！")
    
    def make_account_useful(self):
        def fill_date_field(page, selector, date_str):
            if not date_str:
                return
            ele = page.ele(selector)
            ele.scroll.to_see(center=True)
            js_detect_format = """
                const parts = new Intl.DateTimeFormat().formatToParts(new Date(2023, 11, 31));
                let format = [];
                for (let part of parts) {
                    if (part.type === 'year') format.push('Y');
                    if (part.type === 'month') format.push('M');
                    if (part.type === 'day') format.push('D');
                }
                return format;
            """
            date_format = page.run_js(js_detect_format)
            year, month, day = date_str.split('-')
            date_dict = {'Y': year, 'M': month.zfill(2), 'D': day.zfill(2)}
            ele.click()
            time.sleep(0.1)
            page.actions.type(Keys.LEFT * 3)
            time.sleep(0.1)
            for i, char in enumerate(date_format):
                val = date_dict[char]
                page.actions.type(val)
                time.sleep(0.1)
                if char == 'Y':
                    if i < 2:
                        page.actions.type(Keys.RIGHT)
                        time.sleep(0.1)

        # email = self.account_detail.get('email')
        # password = self.account_detail.get('pwd')
        # location = self.account_detail.get('location')
        
        # self._log("=== 开始执行自动填表流程 ===")
        # btn_selector = 'tag:button@@text():Login'
        # if not self.page.wait.ele_displayed(btn_selector, timeout=3):
        #     login_btn = self.page.ele("tag:a@@href:login")
        #     if login_btn: self.mouse.human_click_ele(login_btn)
        #     time.sleep(3)
        # if not self.page.wait.ele_displayed(btn_selector, timeout=10):
        #     raise BizLogicError(message=f"Can't find selector={btn_selector}")
            
        # recpatchav2_token = ""
        # if self.page.ele('.g-recaptcha') or self.page.ele('xpath://iframe[contains(@src, "recaptcha")]'):
        #     self._log("Solving ReCaptcha...")
        #     recpatchav2_token = self.solve_captcha(self.page.url, "ReCaptchaV2TaskProxyLess", "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0")
        
        # input_ele = self.page.ele('tag:label@@text():Email').next()
        # self.mouse.human_click_ele(input_ele)
        # time.sleep(random.uniform(0.2, 0.6))
        # self.keyboard.type_text(email, humanize=True)
        # time.sleep(random.uniform(0.5, 1.2)) 
    
        # input_ele = self.page.ele('tag:label@@text():Password').next()
        # self.mouse.human_click_ele(input_ele)
        # time.sleep(random.uniform(0.2, 0.6))
        # self.keyboard.type_text(password, humanize=True)
        
        # if recpatchav2_token:
        #     inject_recpatchav2_token_js = f"""
        #     var g = document.getElementById('g-recaptcha-response');
        #     if(g) {{ g.value = "{recpatchav2_token}"; }}
        #     """
        #     self._log("Inject ReCaptchaV2 Token via JS...")
        #     self.page.run_js(inject_recpatchav2_token_js)
        #     time.sleep(random.uniform(0.5, 1.0))
        
        # self._log("Submitting Login...")
        # time.sleep(random.uniform(0.3, 0.8))
        # login_btn = self.page.ele('tag:button@@text():Login')
        # self.mouse.human_click_ele(login_btn)

        # self._log("Waiting for dashboard redirect...")
        # self.page.wait.url_change('login-actions', exclude=True, timeout=45)
        # time.sleep(4)

        # if "login-actions" in self.page.url or "auth" in self.page.url:
        #     raise BizLogicError(message="Login Failed! Invalid credentials or Captcha rejected.")

        # self._log("Waiting for dashboard...")
        # self.page.wait.load_start()
        # time.sleep(5)

        # self._log("Parsing Dashboard for Travel Group...")
        # html = self.page.html
        # js_pattern = r'\\"travelGroups\\":\s*(\[.*?\]),\\"availableCountriesToCreateGroups'
        # js_match = re.search(js_pattern, html, re.DOTALL)
        
        # groups = []
        # if js_match:
        #     json_str = js_match.group(1).replace(r'\"', '"')
        #     groups = json.loads(json_str)

        # travel_group = None
        # for g in groups:
        #     if location in g.get('vacName', ''):
        #         travel_group = g
        #         break
        
        # if not travel_group:
        #     raise BizLogicError(message=f"Travel Group not found for {location}")
        
        # formgroup_id = travel_group.get('formGroupId')
        # self._log(f"Waiting for group button to render: {formgroup_id}")
        # btn_selector = f'tag:a@@data-testid=btn-select-group'            
        # self._log(f"Select group_id={formgroup_id}...")
        # self.mouse.human_click_ele(self.page.ele(btn_selector))
        
        # self._log("Waiting for url redirect...")
        # self.page.wait.url_change('travel-groups', exclude=True, timeout=45)
        # time.sleep(2)

        # if "travel-groups" in self.page.url or "auth" in self.page.url:
        #     raise BizLogicError(message="Redirect to service-level Failed!")
        
        # btn_selector_add = 'tag:button@@data-testid=btn-add-applicant'
        # btn_selector_max = 'tag:button@@data-testid=btn-max-number-of-applicants'
        # error_selector = 'tag:h2@text():Something went wrong'

        # for attempt in range(2):
        #     try:
        #         btn_selector = btn_selector_add
        #         if not self.page.wait.ele_displayed(btn_selector, timeout=10):
        #             btn_selector = btn_selector_max
        #             if not self.page.ele(btn_selector):
        #                 raise BizLogicError(message=f"Can't find selector={btn_selector}")

        #         add_btn = self.page.ele(btn_selector)
        #         add_btn.scroll.to_see(center=True)
        #         time.sleep(random.uniform(0.6, 0.8))
        #         add_btn.click()

        #         if self.page.wait.ele_displayed(error_selector, timeout=5):
        #             raise BizLogicError("Page shows 'Something went wrong'")
        #         break
        #     except Exception as e:
        #         if attempt == 0:
        #             self.page.refresh()
        #             time.sleep(5)
        #         else:
        #             raise BizLogicError(message=f"Click add applicant failed after retry: {e}")
        
        self._log("开始填充表单字段...")
        visa_type = self.account_detail.get("visa_type")
        if visa_type:
            btn = self.page.ele('tag:button@@data-testid=input-visa-type')
            btn.scroll.to_see(center=True)
            btn.click()
            time.sleep(0.5)
            self.page.ele(f'tag:span@@text():{visa_type}').click(by_js=True)
            time.sleep(0.5)

        tavel_purpose = self.account_detail.get("travel_purpose")
        if tavel_purpose:
            btn = self.page.ele('tag:button@@data-testid=input-travel-purpose')
            btn.scroll.to_see(center=True)
            btn.click()
            time.sleep(0.5)
            self.page.ele(f'tag:span@@text():{tavel_purpose}').click(by_js=True)
            time.sleep(0.5)

        application_form_id = self.account_detail.get('application_form_id')
        if application_form_id:
            ele = self.page.ele('tag:input@@data-testid=f_cai')
            ele.scroll.to_see(center=True)
            ele.input(application_form_id)

        last_name = self.account_detail.get('last_name')
        if last_name:
            ele = self.page.ele('tag:input@@data-testid=f_pers_surnames')
            ele.scroll.to_see(center=True)
            ele.input(last_name.upper())

        first_name = self.account_detail.get('first_name')
        if first_name:
            ele = self.page.ele('tag:input@@data-testid=f_pers_givennames')
            ele.scroll.to_see(center=True)
            ele.input(first_name.upper())

        gender = self.account_detail.get('gender')
        if gender:
            try:
                gender = gender.capitalize()
                ele = self.page.ele(f'tag:label@@text():{gender}')
                ele.scroll.to_see(center=True)
                ele.click()
            except Exception as e:
                self._log(e)
            
        fill_date_field(self.page, 'tag:input@@data-testid=f_pers_birth_date', self.account_detail.get('birthday'))

        nationality = self.account_detail.get('nationality')
        if nationality:
            nationality = nationality.title()
            btn = self.page.ele('tag:label@@for=f_pers_nationality').next()
            btn.scroll.to_see(center=True)
            btn.click()
            time.sleep(0.5)
            self.page.ele(f'tag:li@@role=option@@text():{nationality}').click(by_js=True)
            time.sleep(0.5)
            
        province_residence = self.account_detail.get('province_residence')
        if province_residence:
            try:
                province_residence = province_residence.title()
                btn = self.page.ele('tag:label@@for=f_pers_province').next()
                btn.scroll.to_see(center=True)
                btn.click()
                time.sleep(0.5)
                self.page.ele(f'tag:li@@role=option@@text():{province_residence}').click(by_js=True)
                time.sleep(0.5)
            except Exception as e:
                self._log(e)

        passport_type = self.account_detail.get('passport_type')
        if passport_type:
            btn = self.page.ele('tag:label@@for=f_identity_type').next()
            btn.scroll.to_see(center=True)
            btn.click()
            time.sleep(0.5)
            self.page.ele(f'tag:li@@role=option@@text():{passport_type}').click(by_js=True)
            time.sleep(0.5)
            
        passport_no = self.account_detail.get('passport_no')
        if passport_no:
            ele = self.page.ele('tag:input@@data-testid=f_pass_num')
            ele.scroll.to_see(center=True)
            ele.input(passport_no.upper())
            
        passport_issue_date = self.account_detail.get('passport_issue_date')
        if passport_issue_date:
            try:
                fill_date_field(self.page, 'tag:input@@data-testid=fi_passport_issue_date', passport_issue_date)
                time.sleep(0.5)
            except Exception as e:
                self._log(e)
                
        passport_expiry_date = self.account_detail.get('passport_expiry_date')
        if passport_expiry_date:
            try:
                fill_date_field(self.page, 'tag:input@@data-testid=fi_passport_expiry_date', passport_expiry_date)
                time.sleep(0.5)
            except Exception as e:
                self._log(e)
            
        phone_country_code = self.account_detail.get('phone_country_code')
        phone_number = self.account_detail.get('phone_number')
        if phone_country_code:
            div = self.page.ele('tag:label@@for=f_pers_mobile_phone').next()
            btn = div.ele('tag:button')
            btn.scroll.to_see(center=True)
            btn.click()
            time.sleep(0.5)
            self.page.ele(f'tag:li@@role=option@@text():+{phone_country_code}').click(by_js=True)
            time.sleep(0.5)
            div.ele('tag:input@@type:tel').input(phone_number)

        fill_date_field(self.page, 'tag:input@@data-testid=fi_trav_origin_departure_date', self.account_detail.get('departure_origin_date'))
        fill_date_field(self.page, 'tag:input@@data-testid=f_trav_departure_date', self.account_detail.get('arrival_schengen_area_date'))
        fill_date_field(self.page, 'tag:input@@data-testid=f_trav_arrival_date', self.account_detail.get('departure_schengen_area_date'))
            
        submit_btn = self.page.ele('tag:button@@data-testid=btn-submit')
        submit_btn.scroll.to_see(center=True)
        time.sleep(1)
        submit_btn.click()
        time.sleep(6)
        
        confirm_btn = self.page.ele('tag:button@@text():Confirm')
        if confirm_btn:
            confirm_btn.scroll.to_see(center=True)
            confirm_btn.click()
            time.sleep(6)
        self._log("✅ 自动填表提交成功！")
        
    def upload_account_to_server(self):
        api_url = 'https://api.text.skin/api/account/add'
        api_token = 'tok_e946329a60ff45ba807f3f41b0e8b7fc'

        headers = {
            'accept': 'application/json',
            'Authorization': f'Bearer {api_token}',
            'Content-Type': 'application/json'
        }

        payload = {
            "pool_name": self.account_detail.get("pool_name", "default_pool"),
            "username": self.account_detail.get("email"),
            "password": self.account_detail.get("pwd"),
            "extra_data": self.account_detail
        }

        try:
            self._log(f"Uploading account {self.account_detail['email']} to server...")
            resp = requests.post(api_url, json=payload, headers=headers, timeout=10)
            if resp.status_code == 200:
                self._log(f"✅ [API Upload Success] Server responded: {resp.text}")
                return True
            else:
                self._log(f"❌ [API Upload Failed] Status: {resp.status_code}, Body: {resp.text}")
                return False
        except Exception as e:
            self._log(f"❌ [API Upload Error]: {e}")
            return False

    def cleanup(self):
        self._log("Cleaning up resources...")
        if self.page:
            try: self.page.quit()
            except: pass

# ================= PyQt5 GUI 主窗口 =================
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("TLS 批量注册工具 v1.0 (PyQt5)")
        self.resize(1000, 700)

        self.bot = None
        self.profiles = []
        self.capsolver_key = os.getenv("CAPSOLVER_KEY", "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A")

        # 实例化信号类并绑定槽函数
        self.signals = GuiSignals()
        self.signals.log_signal.connect(self.append_log)
        self.signals.status_signal.connect(self.update_table_status)

        self.init_ui()

    def init_ui(self):
        main_widget = QWidget()
        main_layout = QVBoxLayout(main_widget)

        # 1. 基础设置区域
        group_settings = QGroupBox("1. 基础设置")
        layout_settings = QGridLayout()
        
        layout_settings.addWidget(QLabel("地区 (如 CN-Beijing):"), 0, 0)
        self.input_region = QLineEdit("CN-Beijing")
        layout_settings.addWidget(self.input_region, 0, 1)

        layout_settings.addWidget(QLabel("代理池名 (从proxies.json读取):"), 0, 2)
        self.input_pool = QLineEdit("local")
        layout_settings.addWidget(self.input_pool, 0, 3)

        layout_settings.addWidget(QLabel("目标 URL:"), 1, 0)
        self.input_url = QLineEdit("https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr")
        layout_settings.addWidget(self.input_url, 1, 1, 1, 3)

        group_settings.setLayout(layout_settings)
        main_layout.addWidget(group_settings)

        # 2. 生成区域
        group_gen = QGroupBox("2. 生成虚拟信息")
        layout_gen = QHBoxLayout()
        layout_gen.addWidget(QLabel("生成数量 (1-100):"))
        self.spin_count = QSpinBox()
        self.spin_count.setRange(1, 100)
        self.spin_count.setValue(5)
        layout_gen.addWidget(self.spin_count)

        self.btn_generate = QPushButton("生成虚拟信息")
        self.btn_generate.setStyleSheet("background-color: lightblue;")
        self.btn_generate.clicked.connect(self.action_generate)
        layout_gen.addWidget(self.btn_generate)
        layout_gen.addStretch()

        group_gen.setLayout(layout_gen)
        main_layout.addWidget(group_gen)

        # 3. 表格展示区域
        self.table = QTableWidget()
        self.table.setColumnCount(5)
        self.table.setHorizontalHeaderLabels(["序号", "邮箱", "密码", "姓名", "当前状态"])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
        main_layout.addWidget(self.table)

        # 4. 浏览器操作区域
        group_actions = QGroupBox("3. 浏览器操作 (需先在表格中点击选中一行)")
        layout_actions = QHBoxLayout()

        self.btn_start = QPushButton("[全局] 启动浏览器")
        self.btn_start.clicked.connect(self.action_start_browser)
        layout_actions.addWidget(self.btn_start)

        self.btn_register = QPushButton("1. 自动注册并激活")
        self.btn_register.setStyleSheet("background-color: #d0f0c0;")
        self.btn_register.clicked.connect(self.action_register)
        layout_actions.addWidget(self.btn_register)

        self.btn_fill = QPushButton("2. 自动填表")
        self.btn_fill.setStyleSheet("background-color: #ffd700;")
        self.btn_fill.clicked.connect(self.action_fill)
        layout_actions.addWidget(self.btn_fill)

        self.btn_upload = QPushButton("3. 上报并标记已用")
        self.btn_upload.setStyleSheet("background-color: #ffb6c1;")
        self.btn_upload.clicked.connect(self.action_upload)
        layout_actions.addWidget(self.btn_upload)

        self.btn_close = QPushButton("[全局] 关闭浏览器")
        self.btn_close.setStyleSheet("color: red;")
        self.btn_close.clicked.connect(self.action_close_browser)
        layout_actions.addWidget(self.btn_close)

        group_actions.setLayout(layout_actions)
        main_layout.addWidget(group_actions)

        # 5. 日志区域
        group_log = QGroupBox("运行日志")
        layout_log = QVBoxLayout()
        self.text_log = QTextEdit()
        self.text_log.setReadOnly(True)
        layout_log.addWidget(self.text_log)
        group_log.setLayout(layout_log)
        main_layout.addWidget(group_log)

        self.setCentralWidget(main_widget)

    # ---------------- 槽函数：UI 更新 ----------------
    def append_log(self, text):
        self.text_log.append(text)
        self.text_log.moveCursor(self.text_log.textCursor().End)

    def update_table_status(self, row, status):
        item = QTableWidgetItem(status)
        item.setTextAlignment(Qt.AlignCenter)
        self.table.setItem(row, 4, item)

    # ---------------- 业务逻辑触发 ----------------
    def run_in_thread(self, target_func, *args):
        t = threading.Thread(target=target_func, args=args, daemon=True)
        t.start()

    def action_generate(self):
        count = self.spin_count.value()
        region = self.input_region.text().strip()
        pool = self.input_pool.text().strip()

        self.append_log(f"开始生成 {count} 个 {region} 地区的虚拟信息...")
        self.profiles.clear()
        self.table.setRowCount(0)

        for i in range(count):
            try:
                acc = generate_random_account_detail(region)
                acc['pool_name'] = pool
                self.profiles.append(acc)

                self.table.insertRow(i)
                self.table.setItem(i, 0, QTableWidgetItem(str(i+1)))
                self.table.setItem(i, 1, QTableWidgetItem(acc.get('email', '')))
                self.table.setItem(i, 2, QTableWidgetItem(acc.get('pwd', '')))
                
                name = f"{acc.get('first_name','')} {acc.get('last_name','')}"
                self.table.setItem(i, 3, QTableWidgetItem(name))
                
                self.update_table_status(i, "就绪")
            except Exception as e:
                self.append_log(f"生成时出错: {e}")
        
        self.append_log("✅ 虚拟信息生成完毕！")

    def action_start_browser(self):
        pool = self.input_pool.text().strip()
        url = self.input_url.text().strip()

        def _task():
            try:
                self.signals.log_signal.emit(f"正在读取代理池 {pool} ...")
                proxies = load_proxies(pool)
                proxy = random.choice(proxies)
                self.signals.log_signal.emit(f"已选取代理: {proxy.get('ip')}")

                self.bot = TlsRegistrator(
                    tls_url=url, 
                    proxy_config=proxy, 
                    capsolver_key=self.capsolver_key,
                    signals=self.signals
                )
                self.bot.init_browser()
            except Exception as e:
                self.signals.log_signal.emit(f"❌ 浏览器启动失败: {e}")

        self.run_in_thread(_task)

    def _get_selected_row(self):
        row = self.table.currentRow()
        if row < 0:
            QMessageBox.warning(self, "警告", "请先在表格中点击选中需要操作的虚拟账号！")
            return None
        return row

    def action_register(self):
        row = self._get_selected_row()
        if row is None: return
        if not self.bot:
            QMessageBox.warning(self, "警告", "请先启动浏览器！")
            return

        profile = self.profiles[row]

        def _task():
            try:
                self.bot.account_detail = profile
                self.signals.status_signal.emit(row, "注册中...")
                sent_at = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
                
                self.bot.register()
                self.signals.status_signal.emit(row, "激活中...")
                
                self.bot.activate(sent_at=sent_at)
                self.signals.status_signal.emit(row, "注册激活完成")
            except Exception as e:
                self.signals.log_signal.emit(f"❌ 注册过程异常: {e}")
                self.signals.status_signal.emit(row, "注册失败")

        self.run_in_thread(_task)

    def action_fill(self):
        row = self._get_selected_row()
        if row is None: return
        if not self.bot:
            QMessageBox.warning(self, "警告", "请先启动浏览器！")
            return

        profile = self.profiles[row]

        def _task():
            try:
                self.bot.account_detail = profile
                self.signals.status_signal.emit(row, "填表中...")
                
                self.bot.make_account_useful()
                
                self.signals.status_signal.emit(row, "填表完成")
            except Exception as e:
                self.signals.log_signal.emit(f"❌ 填表过程异常: {e}")
                self.signals.status_signal.emit(row, "填表失败")

        self.run_in_thread(_task)

    def action_upload(self):
        row = self._get_selected_row()
        if row is None: return
        if not self.bot: return

        profile = self.profiles[row]

        def _task():
            try:
                self.bot.account_detail = profile
                self.signals.status_signal.emit(row, "上报中...")
                
                success = self.bot.upload_account_to_server()
                if success:
                    self.signals.status_signal.emit(row, "已使用(上报成功)")
                else:
                    self.signals.status_signal.emit(row, "上报失败")
            except Exception as e:
                self.signals.log_signal.emit(f"❌ 上报异常: {e}")
                self.signals.status_signal.emit(row, "上报异常")

        self.run_in_thread(_task)

    def action_close_browser(self):
        if self.bot:
            self.bot.cleanup()
            self.bot = None
            self.append_log("✅ 浏览器已关闭。")
            
    def closeEvent(self, event):
        """主窗口关闭时，确保清理资源"""
        if self.bot:
            self.bot.cleanup()
        event.accept()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())