#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations

import os
import threading
import time

import database
import email_service
from api_browser import APIBrowser
from app_config import get_config
from app_logger import LoggerAdapter, get_logger
from services.checkpoints import get_checkpoint_mgr
from services.client_log import log_to_client
from services.proxy import get_proxy_from_api
from services.runtime import get_socketio
from services.screenshots import take_screenshot_for_account
from services.state import (
    safe_get_account,
    safe_remove_task,
    safe_remove_task_status,
    safe_set_task_status,
    safe_update_task_status,
)
from services.task_batches import _batch_task_record_result, _get_batch_id_from_source
from services.task_scheduler import TaskScheduler
from task_checkpoint import TaskStage

logger = get_logger("app")
config = get_config()

# 并发默认值（启动后会由系统配置覆盖并调用 update_limits）
max_concurrent_per_account = config.MAX_CONCURRENT_PER_ACCOUNT
max_concurrent_global = config.MAX_CONCURRENT_GLOBAL
_SOURCE_UNSET = object()


def _emit(event: str, data: object, *, room: str | None = None) -> None:
    try:
        socketio = get_socketio()
        socketio.emit(event, data, room=room)
    except Exception:
        pass


_task_scheduler = None
_task_scheduler_lock = threading.Lock()


def get_task_scheduler() -> TaskScheduler:
    """获取全局任务调度器（单例）"""
    global _task_scheduler
    with _task_scheduler_lock:
        if _task_scheduler is None:
            try:
                max_queue_size = int(os.environ.get("TASK_QUEUE_MAXSIZE", "1000"))
            except Exception:
                max_queue_size = 1000
            _task_scheduler = TaskScheduler(
                max_global=max_concurrent_global,
                max_per_user=max_concurrent_per_account,
                max_queue_size=max_queue_size,
                run_task_fn=run_task,
            )
        return _task_scheduler


def submit_account_task(
    user_id: int,
    account_id: str,
    browse_type: str,
    enable_screenshot: bool = True,
    source: str = "manual",
    retry_count: int = 0,
    done_callback=None,
):
    """统一入口：提交账号任务进入队列"""
    account = safe_get_account(user_id, account_id)
    if not account:
        return False, "账号不存在"
    if getattr(account, "is_running", False):
        return False, "任务已在运行中"

    try:
        is_vip_user = bool(database.is_user_vip(user_id))
    except Exception:
        is_vip_user = False

    account.is_running = True
    account.should_stop = False
    account.status = "排队中" + (" (VIP)" if is_vip_user else "")

    safe_set_task_status(
        account_id,
        {
            "user_id": user_id,
            "username": account.username,
            "status": "排队中",
            "detail_status": "等待资源" + (" [VIP优先]" if is_vip_user else ""),
            "browse_type": browse_type,
            "start_time": time.time(),
            "source": source,
            "progress": {"items": 0, "attachments": 0},
            "is_vip": is_vip_user,
        },
    )
    _emit("account_update", account.to_dict(), room=f"user_{user_id}")

    scheduler = get_task_scheduler()
    ok, message = scheduler.submit_task(
        user_id=user_id,
        account_id=account_id,
        browse_type=browse_type,
        enable_screenshot=enable_screenshot,
        source=source,
        retry_count=retry_count,
        is_vip=is_vip_user,
        done_callback=done_callback,
    )
    if not ok:
        account.is_running = False
        account.status = "未开始"
        safe_remove_task_status(account_id)
        _emit("account_update", account.to_dict(), room=f"user_{user_id}")
        return False, message

    log_to_client(message + (" [VIP优先]" if is_vip_user else ""), user_id, account_id)
    return True, message


def _account_display_name(account) -> str:
    return account.remark if account.remark else account.username


def _record_batch_result(batch_id, account, total_items: int, total_attachments: int) -> None:
    if not batch_id:
        return
    _batch_task_record_result(
        batch_id=batch_id,
        account_name=_account_display_name(account),
        screenshot_path=None,
        total_items=total_items,
        total_attachments=total_attachments,
    )


def _close_account_automation(account, *, on_error=None) -> bool:
    automation = getattr(account, "automation", None)
    if not automation:
        return False

    closed = False
    try:
        automation.close()
        closed = True
    except Exception as e:
        if on_error:
            try:
                on_error(e)
            except Exception:
                pass
    finally:
        account.automation = None

    return closed


def _create_task_log(
    *,
    user_id: int,
    account_id: str,
    account,
    browse_type: str,
    status: str,
    total_items: int,
    total_attachments: int,
    error_message: str,
    duration: int,
    source=_SOURCE_UNSET,
) -> None:
    payload = {
        "user_id": user_id,
        "account_id": account_id,
        "username": account.username,
        "browse_type": browse_type,
        "status": status,
        "total_items": total_items,
        "total_attachments": total_attachments,
        "error_message": error_message,
        "duration": duration,
    }
    if source is not _SOURCE_UNSET:
        payload["source"] = source
    database.create_task_log(**payload)


def _handle_stop_requested(
    *,
    account,
    user_id: int,
    account_id: str,
    batch_id,
    remove_task_status: bool = False,
    record_batch: bool = False,
) -> bool:
    if not account.should_stop:
        return False

    log_to_client("任务已取消", user_id, account_id)
    account.status = "已停止"
    account.is_running = False
    if remove_task_status:
        safe_remove_task_status(account_id)
    _emit("account_update", account.to_dict(), room=f"user_{user_id}")

    if record_batch and batch_id:
        _record_batch_result(batch_id=batch_id, account=account, total_items=0, total_attachments=0)
    return True


def _resolve_proxy_config(user_id: int, account_id: str, account):
    proxy_config = None
    system_config = database.get_system_config()
    if system_config.get("proxy_enabled") != 1:
        return proxy_config

    proxy_api_url = system_config.get("proxy_api_url", "").strip()
    if not proxy_api_url:
        log_to_client("⚠ 代理已启用但未配置API地址", user_id, account_id)
        return proxy_config

    log_to_client("正在获取代理IP...", user_id, account_id)
    proxy_server = get_proxy_from_api(proxy_api_url, max_retries=3)
    if proxy_server:
        proxy_config = {"server": proxy_server}
        log_to_client(f"[OK] 将使用代理: {proxy_server}", user_id, account_id)
        account.proxy_config = proxy_config
    else:
        log_to_client("✗ 代理获取失败，将不使用代理继续", user_id, account_id)
    return proxy_config


def _refresh_account_remark(api_browser, account, user_id: int, account_id: str) -> None:
    if account.remark:
        return
    try:
        real_name = api_browser.get_real_name()
        if not real_name:
            return
        account.remark = real_name
        database.update_account_remark(account_id, real_name)
        _emit("account_update", account.to_dict(), room=f"user_{user_id}")
        logger.info(f"[自动备注] 账号 {account.username} 自动设置备注为: {real_name}")
    except Exception as e:
        logger.warning(f"[自动备注] 获取姓名失败: {e}")


def _handle_login_failed(
    *,
    account,
    user_id: int,
    account_id: str,
    browse_type: str,
    source: str,
    task_start_time: float,
    task_id: str,
    checkpoint_mgr,
    time_module,
) -> None:
    error_message = "登录失败"
    log_to_client(f"❌ {error_message}", user_id, account_id)

    is_suspended = database.increment_account_login_fail(account_id, error_message)
    if is_suspended:
        log_to_client("⚠ 该账号连续3次密码错误，已自动暂停", user_id, account_id)
        log_to_client("请在前台修改密码后才能继续使用", user_id, account_id)

    retry_action = checkpoint_mgr.record_error(task_id, error_message)
    if retry_action == "paused":
        logger.warning(f"[断点] 任务 {task_id} 已暂停(登录失败)")

    account.status = "登录失败"
    account.is_running = False
    _create_task_log(
        user_id=user_id,
        account_id=account_id,
        account=account,
        browse_type=browse_type,
        status="failed",
        total_items=0,
        total_attachments=0,
        error_message=error_message,
        duration=int(time_module.time() - task_start_time),
        source=source,
    )
    _emit("account_update", account.to_dict(), room=f"user_{user_id}")


def _execute_single_attempt(
    *,
    account,
    user_id: int,
    account_id: str,
    browse_type: str,
    source: str,
    checkpoint_mgr,
    task_id: str,
    task_start_time: float,
    time_module,
):
    proxy_config = _resolve_proxy_config(user_id=user_id, account_id=account_id, account=account)

    checkpoint_mgr.update_stage(task_id, TaskStage.STARTING, progress_percent=10)

    def custom_log(message: str):
        log_to_client(message, user_id, account_id)

    log_to_client("开始登录...", user_id, account_id)
    safe_update_task_status(account_id, {"detail_status": "正在登录"})
    checkpoint_mgr.update_stage(task_id, TaskStage.LOGGING_IN, progress_percent=25)

    with APIBrowser(log_callback=custom_log, proxy_config=proxy_config) as api_browser:
        if not api_browser.login(account.username, account.password):
            _handle_login_failed(
                account=account,
                user_id=user_id,
                account_id=account_id,
                browse_type=browse_type,
                source=source,
                task_start_time=task_start_time,
                task_id=task_id,
                checkpoint_mgr=checkpoint_mgr,
                time_module=time_module,
            )
            return "login_failed", None

        log_to_client("[OK] 首次登录成功，刷新登录时间...", user_id, account_id)

        if api_browser.login(account.username, account.password):
            log_to_client("[OK] 二次登录成功！", user_id, account_id)
        else:
            log_to_client("⚠ 二次登录失败，继续使用首次登录状态", user_id, account_id)

        api_browser.save_cookies_for_screenshot(account.username)
        database.reset_account_login_status(account_id)
        _refresh_account_remark(api_browser, account, user_id, account_id)

        safe_update_task_status(account_id, {"detail_status": "正在浏览"})
        log_to_client(f"开始浏览 '{browse_type}' 内容...", user_id, account_id)
        account.total_items = 0
        safe_update_task_status(account_id, {"progress": {"items": 0, "attachments": 0}})

        def should_stop():
            return account.should_stop

        def on_browse_progress(progress: dict):
            try:
                total_items = int(progress.get("total_items") or 0)
                browsed_items = int(progress.get("browsed_items") or 0)
                if total_items > 0:
                    account.total_items = total_items
                safe_update_task_status(account_id, {"progress": {"items": browsed_items, "attachments": 0}})
            except Exception:
                pass

        checkpoint_mgr.update_stage(task_id, TaskStage.BROWSING, progress_percent=50)
        result = api_browser.browse_content(
            browse_type=browse_type,
            should_stop_callback=should_stop,
            progress_callback=on_browse_progress,
        )
        return "ok", result


def _record_success_without_screenshot(
    *,
    account,
    user_id: int,
    account_id: str,
    browse_type: str,
    source: str,
    task_start_time: float,
    result,
    batch_id,
    time_module,
) -> bool:
    _create_task_log(
        user_id=user_id,
        account_id=account_id,
        account=account,
        browse_type=browse_type,
        status="success",
        total_items=result.total_items,
        total_attachments=result.total_attachments,
        error_message="",
        duration=int(time_module.time() - task_start_time),
        source=source,
    )
    if batch_id:
        _record_batch_result(
            batch_id=batch_id,
            account=account,
            total_items=result.total_items,
            total_attachments=result.total_attachments,
        )
        return True

    if source and source.startswith("user_scheduled"):
        try:
            user_info = database.get_user_by_id(user_id)
            if user_info and user_info.get("email") and database.get_user_email_notify(user_id):
                email_service.send_task_complete_email_async(
                    user_id=user_id,
                    email=user_info["email"],
                    username=user_info["username"],
                    account_name=_account_display_name(account),
                    browse_type=browse_type,
                    total_items=result.total_items,
                    total_attachments=result.total_attachments,
                    screenshot_path=None,
                    log_callback=lambda msg: log_to_client(msg, user_id, account_id),
                )
        except Exception as email_error:
            logger.warning(f"发送任务完成邮件失败: {email_error}")

    return False


def _is_timeout_error(error_msg: str) -> bool:
    return "Timeout" in error_msg or "timeout" in error_msg


def _record_failed_task_log(
    *,
    account,
    user_id: int,
    account_id: str,
    browse_type: str,
    source: str,
    total_items: int,
    total_attachments: int,
    error_message: str,
    task_start_time: float,
    time_module,
) -> None:
    account.status = "出错"
    _create_task_log(
        user_id=user_id,
        account_id=account_id,
        account=account,
        browse_type=browse_type,
        status="failed",
        total_items=total_items,
        total_attachments=total_attachments,
        error_message=error_message,
        duration=int(time_module.time() - task_start_time),
        source=source,
    )


def _handle_failed_browse_result(
    *,
    account,
    result,
    error_msg: str,
    user_id: int,
    account_id: str,
    browse_type: str,
    source: str,
    attempt: int,
    max_attempts: int,
    task_start_time: float,
    time_module,
) -> str:
    if _is_timeout_error(error_msg):
        log_to_client(f"⚠ 检测到超时错误: {error_msg}", user_id, account_id)

        close_ok = _close_account_automation(
            account,
            on_error=lambda e: logger.debug(f"关闭超时浏览器实例失败: {e}"),
        )
        if close_ok:
            log_to_client("已关闭超时的浏览器实例", user_id, account_id)

        if attempt < max_attempts:
            log_to_client(f"⚠ 代理可能速度过慢，将换新IP重试 ({attempt}/{max_attempts})", user_id, account_id)
            time_module.sleep(2)
            return "continue"

        log_to_client(f"❌ 已达到最大重试次数({max_attempts})，任务失败", user_id, account_id)
        _record_failed_task_log(
            account=account,
            user_id=user_id,
            account_id=account_id,
            browse_type=browse_type,
            source=source,
            total_items=result.total_items,
            total_attachments=result.total_attachments,
            error_message=f"重试{max_attempts}次后仍失败: {error_msg}",
            task_start_time=task_start_time,
            time_module=time_module,
        )
        return "break"

    log_to_client(f"浏览出错: {error_msg}", user_id, account_id)
    _record_failed_task_log(
        account=account,
        user_id=user_id,
        account_id=account_id,
        browse_type=browse_type,
        source=source,
        total_items=result.total_items,
        total_attachments=result.total_attachments,
        error_message=error_msg,
        task_start_time=task_start_time,
        time_module=time_module,
    )
    return "break"


def _handle_attempt_exception(
    *,
    account,
    error_msg: str,
    user_id: int,
    account_id: str,
    browse_type: str,
    source: str,
    attempt: int,
    max_attempts: int,
    task_start_time: float,
    time_module,
) -> str:
    _close_account_automation(
        account,
        on_error=lambda e: logger.debug(f"关闭浏览器实例失败: {e}"),
    )

    if _is_timeout_error(error_msg):
        log_to_client(f"⚠ 执行超时: {error_msg}", user_id, account_id)
        if attempt < max_attempts:
            log_to_client(f"⚠ 将换新IP重试 ({attempt}/{max_attempts})", user_id, account_id)
            time_module.sleep(2)
            return "continue"

        log_to_client(f"❌ 已达到最大重试次数({max_attempts})，任务失败", user_id, account_id)
        _record_failed_task_log(
            account=account,
            user_id=user_id,
            account_id=account_id,
            browse_type=browse_type,
            source=source,
            total_items=account.total_items,
            total_attachments=account.total_attachments,
            error_message=f"重试{max_attempts}次后仍失败: {error_msg}",
            task_start_time=task_start_time,
            time_module=time_module,
        )
        return "break"

    log_to_client(f"任务执行异常: {error_msg}", user_id, account_id)
    _record_failed_task_log(
        account=account,
        user_id=user_id,
        account_id=account_id,
        browse_type=browse_type,
        source=source,
        total_items=account.total_items,
        total_attachments=account.total_attachments,
        error_message=error_msg,
        task_start_time=task_start_time,
        time_module=time_module,
    )
    return "break"


def _schedule_auto_retry(
    *,
    user_id: int,
    account_id: str,
    browse_type: str,
    enable_screenshot: bool,
    source: str,
    retry_count: int,
) -> None:
    def delayed_retry_submit():
        fresh_account = safe_get_account(user_id, account_id)
        if not fresh_account:
            log_to_client("自动重试取消: 账户不存在", user_id, account_id)
            return
        if fresh_account.should_stop:
            log_to_client("自动重试取消: 任务已被停止", user_id, account_id)
            return

        log_to_client(f"🔄 开始第 {retry_count + 1} 次自动重试...", user_id, account_id)
        ok, msg = submit_account_task(
            user_id=user_id,
            account_id=account_id,
            browse_type=browse_type,
            enable_screenshot=enable_screenshot,
            source=source,
            retry_count=retry_count + 1,
        )
        if not ok:
            log_to_client(f"自动重试提交失败: {msg}", user_id, account_id)

    try:
        threading.Timer(5, delayed_retry_submit).start()
    except Exception:
        delayed_retry_submit()


def _finalize_task_run(
    *,
    account,
    user_id: int,
    account_id: str,
    browse_type: str,
    source: str,
    enable_screenshot: bool,
    retry_count: int,
    max_auto_retry: int,
    task_start_time: float,
    result,
    batch_id,
    batch_recorded: bool,
) -> None:
    final_status = str(account.status or "")
    account.is_running = False
    screenshot_submitted = False

    _close_account_automation(
        account,
        on_error=lambda e: log_to_client(f"关闭主任务浏览器时出错: {str(e)}", user_id, account_id),
    )

    safe_remove_task(account_id)
    safe_remove_task_status(account_id)

    if final_status == "已完成" and not account.should_stop:
        account.status = "已完成"
        _emit("account_update", account.to_dict(), room=f"user_{user_id}")

        if enable_screenshot:
            log_to_client("等待2秒后开始截图...", user_id, account_id)
            account.status = "等待截图"
            _emit("account_update", account.to_dict(), room=f"user_{user_id}")

            safe_set_task_status(
                account_id,
                {
                    "user_id": user_id,
                    "username": account.username,
                    "status": "排队中",
                    "detail_status": "等待截图资源",
                    "browse_type": browse_type,
                    "start_time": time.time(),
                    "source": source,
                    "progress": {
                        "items": result.total_items if result else 0,
                        "attachments": result.total_attachments if result else 0,
                    },
                },
            )

            browse_result_dict = {
                "total_items": result.total_items if result else 0,
                "total_attachments": result.total_attachments if result else 0,
            }
            screenshot_submitted = True
            threading.Thread(
                target=take_screenshot_for_account,
                args=(user_id, account_id, browse_type, source, task_start_time, browse_result_dict),
                daemon=True,
            ).start()
        else:
            account.status = "未开始"
            _emit("account_update", account.to_dict(), room=f"user_{user_id}")
            log_to_client("截图功能已禁用,跳过截图", user_id, account_id)
    else:
        if final_status == "出错" and retry_count < max_auto_retry:
            log_to_client(f"⚠ 任务执行失败，5秒后自动重试 ({retry_count + 1}/{max_auto_retry})...", user_id, account_id)
            account.status = "等待重试"
            _emit("account_update", account.to_dict(), room=f"user_{user_id}")
            _schedule_auto_retry(
                user_id=user_id,
                account_id=account_id,
                browse_type=browse_type,
                enable_screenshot=enable_screenshot,
                source=source,
                retry_count=retry_count,
            )
        elif final_status in ["登录失败", "出错"]:
            account.status = final_status
            _emit("account_update", account.to_dict(), room=f"user_{user_id}")
        else:
            account.status = "未开始"
            _emit("account_update", account.to_dict(), room=f"user_{user_id}")

    if batch_id and (not screenshot_submitted) and (not batch_recorded) and account.status != "等待重试":
        _record_batch_result(
            batch_id=batch_id,
            account=account,
            total_items=getattr(account, "total_items", 0) or 0,
            total_attachments=getattr(account, "total_attachments", 0) or 0,
        )


def run_task(user_id, account_id, browse_type, enable_screenshot=True, source="manual", retry_count=0):
    """运行自动化任务

    Args:
        retry_count: 当前重试次数，用于自动重试机制（最多重试2次）
    """
    MAX_AUTO_RETRY = 2  # 最大自动重试次数
    LoggerAdapter("app", {"user_id": user_id, "account_id": account_id, "source": source}).debug(
        f"run_task enable_screenshot={enable_screenshot} ({type(enable_screenshot).__name__}), retry={retry_count}"
    )

    account = safe_get_account(user_id, account_id)
    if not account:
        return
    batch_id = _get_batch_id_from_source(source)
    batch_recorded = False

    checkpoint_mgr = get_checkpoint_mgr()

    import time as time_module

    task_start_time = time_module.time()
    result = None

    try:
        if _handle_stop_requested(
            account=account,
            user_id=user_id,
            account_id=account_id,
            batch_id=batch_id,
            remove_task_status=True,
            record_batch=True,
        ):
            return

        try:
            if _handle_stop_requested(
                account=account,
                user_id=user_id,
                account_id=account_id,
                batch_id=batch_id,
            ):
                return

            task_id = checkpoint_mgr.create_checkpoint(
                user_id=user_id, account_id=account_id, username=account.username, browse_type=browse_type
            )
            logger.info(f"[断点] 任务 {task_id} 已创建")

            task_start_time = time_module.time()

            account.status = "运行中"
            _emit("account_update", account.to_dict(), room=f"user_{user_id}")
            account.last_browse_type = browse_type

            safe_update_task_status(
                account_id, {"status": "运行中", "detail_status": "初始化", "start_time": task_start_time}
            )

            max_attempts = 3

            for attempt in range(1, max_attempts + 1):
                try:
                    if attempt > 1:
                        log_to_client(f"🔄 第 {attempt} 次尝试（共{max_attempts}次）...", user_id, account_id)

                    attempt_status, result = _execute_single_attempt(
                        account=account,
                        user_id=user_id,
                        account_id=account_id,
                        browse_type=browse_type,
                        source=source,
                        checkpoint_mgr=checkpoint_mgr,
                        task_id=task_id,
                        task_start_time=task_start_time,
                        time_module=time_module,
                    )
                    if attempt_status == "login_failed":
                        return

                    account.total_items = result.total_items
                    account.total_attachments = result.total_attachments

                    if result.success:
                        log_to_client(
                            f"浏览完成! 共 {result.total_items} 条内容，{result.total_attachments} 个附件",
                            user_id,
                            account_id,
                        )
                        safe_update_task_status(
                            account_id,
                            {
                                "detail_status": "浏览完成",
                                "progress": {"items": result.total_items, "attachments": result.total_attachments},
                            },
                        )
                        account.status = "已完成"
                        checkpoint_mgr.update_stage(task_id, TaskStage.COMPLETING, progress_percent=95)
                        checkpoint_mgr.complete_task(task_id, success=True)
                        logger.info(f"[断点] 任务 {task_id} 已完成")

                        if not enable_screenshot:
                            batch_recorded = _record_success_without_screenshot(
                                account=account,
                                user_id=user_id,
                                account_id=account_id,
                                browse_type=browse_type,
                                source=source,
                                task_start_time=task_start_time,
                                result=result,
                                batch_id=batch_id,
                                time_module=time_module,
                            )
                        break

                    error_msg = result.error_message
                    action = _handle_failed_browse_result(
                        account=account,
                        result=result,
                        error_msg=error_msg,
                        user_id=user_id,
                        account_id=account_id,
                        browse_type=browse_type,
                        source=source,
                        attempt=attempt,
                        max_attempts=max_attempts,
                        task_start_time=task_start_time,
                        time_module=time_module,
                    )
                    if action == "continue":
                        continue
                    break

                except Exception as retry_error:
                    error_msg = str(retry_error)
                    action = _handle_attempt_exception(
                        account=account,
                        error_msg=error_msg,
                        user_id=user_id,
                        account_id=account_id,
                        browse_type=browse_type,
                        source=source,
                        attempt=attempt,
                        max_attempts=max_attempts,
                        task_start_time=task_start_time,
                        time_module=time_module,
                    )
                    if action == "continue":
                        continue
                    break

        except Exception as e:
            error_msg = str(e)
            log_to_client(f"任务执行出错: {error_msg}", user_id, account_id)
            account.status = "出错"
            _create_task_log(
                user_id=user_id,
                account_id=account_id,
                account=account,
                browse_type=browse_type,
                status="failed",
                total_items=account.total_items,
                total_attachments=account.total_attachments,
                error_message=error_msg,
                duration=int(time_module.time() - task_start_time),
                source=source,
            )

        finally:
            _finalize_task_run(
                account=account,
                user_id=user_id,
                account_id=account_id,
                browse_type=browse_type,
                source=source,
                enable_screenshot=enable_screenshot,
                retry_count=retry_count,
                max_auto_retry=MAX_AUTO_RETRY,
                task_start_time=task_start_time,
                result=result,
                batch_id=batch_id,
                batch_recorded=batch_recorded,
            )

    finally:
        pass
