import asyncio
import importlib
import json
from asyncio import iscoroutinefunction
from collections.abc import Callable
from datetime import datetime, timedelta
from typing import Any

from apscheduler.events import EVENT_ALL, SchedulerEvent
from apscheduler.executors.asyncio import AsyncIOExecutor
from apscheduler.executors.pool import ProcessPoolExecutor
from apscheduler.job import Job
from apscheduler.jobstores.memory import MemoryJobStore
from apscheduler.jobstores.redis import RedisJobStore
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.combining import OrTrigger
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.date import DateTrigger
from redis import asyncio as aioredis
from sqlalchemy.engine import Engine
from sqlalchemy.ext.asyncio import AsyncEngine

import module_task  # noqa: F401
from common.constant import LockConstant
from config.database import (
    SYNC_SQLALCHEMY_DATABASE_URL,
    create_async_db_engine,
    create_async_session_local,
    create_sync_db_engine,
    create_sync_session_local,
)
from config.env import AppConfig, LogConfig, RedisConfig
from module_admin.dao.job_dao import JobDao
from module_admin.entity.vo.job_vo import JobLogModel, JobModel
from module_admin.service.job_log_service import JobLogService
from utils.log_util import logger
from utils.server_util import StartupUtil, WorkerIdUtil


# 重写Cron定时
class MyCronTrigger(CronTrigger):
    CRON_EXPRESSION_LENGTH_MIN = 6
    CRON_EXPRESSION_LENGTH_MAX = 7
    WEEKDAY_COUNT = 5

    @classmethod
    def from_crontab(cls, expr: str, timezone: str | None = None) -> 'MyCronTrigger':
        values = expr.split()
        if len(values) != cls.CRON_EXPRESSION_LENGTH_MIN and len(values) != cls.CRON_EXPRESSION_LENGTH_MAX:
            raise ValueError(f'Wrong number of fields; got {len(values)}, expected 6 or 7')

        second = values[0]
        minute = values[1]
        hour = values[2]
        if '?' in values[3]:
            day = None
        elif 'L' in values[5]:
            day = f'last {values[5].replace("L", "")}'
        elif 'W' in values[3]:
            day = cls.__find_recent_workday(int(values[3].split('W')[0]))
        else:
            day = values[3].replace('L', 'last')
        month = values[4]
        if '?' in values[5] or 'L' in values[5]:
            week = None
        elif '#' in values[5]:
            week = int(values[5].split('#')[1])
        else:
            week = values[5]
        day_of_week = int(values[5].split('#')[0]) - 1 if '#' in values[5] else None
        year = values[6] if len(values) == cls.CRON_EXPRESSION_LENGTH_MAX else None
        return cls(
            second=second,
            minute=minute,
            hour=hour,
            day=day,
            month=month,
            week=week,
            day_of_week=day_of_week,
            year=year,
            timezone=timezone,
        )

    @classmethod
    def __find_recent_workday(cls, day: int) -> int:
        now = datetime.now()
        date = datetime(now.year, now.month, day)
        if date.weekday() < cls.WEEKDAY_COUNT:
            return date.day
        diff = 1
        while True:
            previous_day = date - timedelta(days=diff)
            if previous_day.weekday() < cls.WEEKDAY_COUNT:
                return previous_day.day
            diff += 1


redis_config = {
    'host': RedisConfig.redis_host,
    'port': RedisConfig.redis_port,
    'username': RedisConfig.redis_username,
    'password': RedisConfig.redis_password,
    'db': RedisConfig.redis_database,
}
job_defaults = {'coalesce': False, 'max_instance': 1}
scheduler = AsyncIOScheduler()


class SchedulerUtil:
    """
    定时任务相关方法
    """

    # 分布式锁相关类变量
    _is_leader: bool = False
    _worker_id: str = WorkerIdUtil.get_worker_id(LogConfig.log_worker_id)
    _redis: aioredis.Redis | None = None
    _job_update_time_cache: dict[str, datetime] = {}
    _sync_channel: str = 'scheduler:sync:request'
    _sync_listener_task: asyncio.Task | None = None
    _lock_lost_task: asyncio.Task | None = None
    _sync_task: asyncio.Task | None = None
    _sync_pending: bool = False
    _sync_lock: asyncio.Lock = asyncio.Lock()
    _last_sync_at: datetime | None = None
    _sync_debounce_seconds: float = 0.5
    _sync_min_interval_seconds: float = 2.0
    _reacquire_task: asyncio.Task | None = None
    _reacquire_interval_seconds: float = 5.0
    _sync_async_engine: AsyncEngine | None = None
    _sync_async_sessionmaker: Any | None = None
    _disposed_sync_engines: bool = False

    # 懒加载的同步 Engine 和 SessionLocal
    _jobstore_engine: Engine | None = None
    _listener_engine: Engine | None = None
    _session_local: Any | None = None
    _scheduler_configured: bool = False

    @classmethod
    def _get_jobstore_engine(cls) -> Engine:
        """
        懒加载获取 jobstore 使用的同步 Engine

        :return: 同步 Engine
        """
        if cls._jobstore_engine is None:
            cls._jobstore_engine = create_sync_db_engine(echo=False)
        return cls._jobstore_engine

    @classmethod
    def _get_listener_engine(cls) -> Engine:
        """
        懒加载获取 listener 使用的同步 Engine

        :return: 同步 Engine
        """
        if cls._listener_engine is None:
            cls._listener_engine = create_sync_db_engine()
        return cls._listener_engine

    @classmethod
    def _get_session_local(cls) -> Any:
        """
        懒加载获取同步 SessionLocal

        :return: SessionLocal
        """
        if cls._session_local is None:
            cls._session_local = create_sync_session_local(cls._get_listener_engine())
        return cls._session_local

    @classmethod
    def _configure_scheduler(cls) -> None:
        """
        配置 scheduler（懒加载 jobstore）

        :return: None
        """
        if cls._scheduler_configured:
            return
        job_stores = {
            'default': MemoryJobStore(),
            'sqlalchemy': SQLAlchemyJobStore(url=SYNC_SQLALCHEMY_DATABASE_URL, engine=cls._get_jobstore_engine()),
            'redis': RedisJobStore(**redis_config),
        }
        executors = {'default': AsyncIOExecutor(), 'processpool': ProcessPoolExecutor(5)}
        scheduler.configure(jobstores=job_stores, executors=executors, job_defaults=job_defaults)
        cls._scheduler_configured = True

    @classmethod
    def _should_enable_scheduler_sync(cls) -> bool:
        """
        判断是否需要启用多 worker 的任务状态同步机制

        :return: 是否开启定时同步与监听
        """
        return not AppConfig.app_reload and AppConfig.app_workers > 1

    @classmethod
    async def init_system_scheduler(cls, redis: aioredis.Redis) -> None:
        """
        应用启动时初始化定时任务（使用分布式锁确保只有一个worker启动scheduler）

        :param redis: Redis连接对象
        :return:
        """
        cls._redis = redis
        logger.info(f'🔎 Worker {cls._worker_id} 尝试获取 Application 锁...')

        acquired = await StartupUtil.acquire_startup_log_gate(
            redis=redis,
            lock_key=LockConstant.APP_STARTUP_LOCK_KEY,
            worker_id=cls._worker_id,
            lock_expire_seconds=LockConstant.LOCK_EXPIRE_SECONDS,
        )

        if acquired:
            await cls._start_scheduler_as_leader(redis)
        else:
            cls._is_leader = False
            logger.info(f'⏸️ Worker {cls._worker_id} 未持有 Application 锁，跳过 Scheduler 启动')

    @classmethod
    async def _start_scheduler_as_leader(cls, redis: aioredis.Redis) -> None:
        """
        以 Leader 身份启动 Scheduler（内部方法，调用前需确保已持有锁）

        :param redis: Redis连接对象
        :return: None
        """
        cls._is_leader = True
        cls._disposed_sync_engines = False
        logger.info(f'🎯 Worker {cls._worker_id} 持有 Application 锁，开始启动定时任务...')
        # 懒加载配置 scheduler
        cls._configure_scheduler()
        scheduler.start()

        # 加载数据库中的定时任务
        async with cls._get_sync_async_session() as session:
            job_list = await JobDao.get_job_list_for_scheduler(session)
            for item in job_list:
                cls._add_job_to_scheduler(item)

        # 添加事件监听器
        scheduler.add_listener(cls.scheduler_event_listener, EVENT_ALL)

        if cls._should_enable_scheduler_sync():
            # 添加任务状态同步任务（每30秒从数据库同步一次任务状态）
            scheduler.add_job(
                func=cls.request_scheduler_sync,
                trigger='interval',
                seconds=30,
                id='_scheduler_job_sync',
                name='Scheduler任务同步',
                replace_existing=True,
            )
            cls._sync_listener_task = asyncio.create_task(cls._listen_sync_channel(redis))

        logger.info('✅️ 系统初始定时任务加载成功')

    @classmethod
    def on_lock_lost(cls) -> None:
        """
        锁丢失处理入口

        :return: None
        """
        if not cls._is_leader:
            return
        cls._is_leader = False
        logger.warning(f'⚠️ Worker {cls._worker_id} 失去 Application 锁')
        if cls._lock_lost_task:
            cls._lock_lost_task.cancel()
        cls._lock_lost_task = asyncio.create_task(cls._handle_lock_lost())

    @classmethod
    async def _handle_lock_lost(cls) -> None:
        """
        处理锁丢失后的资源释放

        :return: None
        """
        if cls._sync_listener_task:
            cls._sync_listener_task.cancel()
            try:
                await cls._sync_listener_task
            except asyncio.CancelledError:
                pass
            cls._sync_listener_task = None
        if cls._sync_task:
            cls._sync_task.cancel()
            try:
                await cls._sync_task
            except asyncio.CancelledError:
                pass
            cls._sync_task = None
            cls._sync_pending = False
        if getattr(scheduler, 'running', False):
            scheduler.shutdown()
        await cls._dispose_sync_async_engine()
        cls._dispose_sync_engines()
        cls._ensure_reacquire_task()

    @classmethod
    async def _sync_jobs_from_database(cls) -> None:
        """
        从数据库同步任务状态，确保多worker环境下任务状态一致
        """
        if not cls._is_leader:
            return

        try:
            async with cls._get_sync_async_session() as session:
                db_jobs_all = await JobDao.get_all_job_list_for_scheduler(session)
                db_jobs_enabled = [job for job in db_jobs_all if job.status == '0']
                db_enabled_ids = {str(job.job_id) for job in db_jobs_enabled}
                db_job_map = {str(job.job_id): job for job in db_jobs_enabled}
                db_job_update_time_map = {
                    str(job.job_id): job.update_time for job in db_jobs_enabled if job.update_time is not None
                }
                scheduler_jobs = scheduler.get_jobs()
                scheduler_job_map = {job.id: job for job in scheduler_jobs if not job.id.startswith('_')}
                scheduler_job_ids = set(scheduler_job_map.keys())

                jobs_to_remove = scheduler_job_ids - db_enabled_ids
                for job_id in jobs_to_remove:
                    scheduler.remove_job(job_id=job_id)
                    logger.info(f'🗑️ 同步移除任务: {job_id}')
                    cls._refresh_job_update_cache(job_id, None)

                jobs_to_add = db_enabled_ids - scheduler_job_ids
                for job_id in jobs_to_add:
                    job_info = db_job_map.get(job_id)
                    if job_info:
                        cls._add_job_to_scheduler(job_info)
                        logger.info(f'➕ 同步添加任务: {job_info.job_name}')
                        cls._refresh_job_update_cache(job_id, job_info.update_time)

                jobs_to_update = db_enabled_ids & scheduler_job_ids
                for job_id in jobs_to_update:
                    job_info = db_job_map.get(job_id)
                    scheduler_job = scheduler_job_map.get(job_id)
                    job_update_time = db_job_update_time_map.get(job_id)
                    cls._sync_update_job(job_id, job_info, scheduler_job, job_update_time)

        except Exception as e:
            logger.error(f'❌ 任务同步异常: {e}')

    @classmethod
    def _is_job_config_in_sync(cls, scheduler_job: Job, job_info: JobModel) -> bool:
        """
        判断任务配置是否一致

        :param scheduler_job: 调度器任务对象
        :param job_info: 数据库任务对象
        :return: 是否一致
        """
        job_state = scheduler_job.__getstate__()
        job_kwargs = json.loads(job_info.job_kwargs) if job_info.job_kwargs else None
        job_args = job_info.job_args.split(',') if job_info.job_args else None
        job_executor = job_info.job_executor
        if iscoroutinefunction(cls._import_function(job_info.invoke_target)):
            job_executor = 'default'
        expected = {
            'name': job_info.job_name,
            'executor': job_executor,
            'jobstore': job_info.job_group,
            'misfire_grace_time': 1000000000000 if job_info.misfire_policy == '3' else None,
            'coalesce': job_info.misfire_policy == '2',
            'max_instances': 3 if job_info.concurrent == '0' else 1,
            'trigger': str(MyCronTrigger.from_crontab(job_info.cron_expression)),
            'args': tuple(job_args) if job_args else None,
            'kwargs': job_kwargs if job_kwargs else None,
            'func': str(cls._import_function(job_info.invoke_target)),
        }
        current = {
            'name': job_state.get('name'),
            'executor': job_state.get('executor'),
            'jobstore': scheduler_job._jobstore_alias,
            'misfire_grace_time': job_state.get('misfire_grace_time'),
            'coalesce': job_state.get('coalesce'),
            'max_instances': job_state.get('max_instances'),
            'trigger': str(job_state.get('trigger')),
            'args': job_state.get('args'),
            'kwargs': job_state.get('kwargs'),
            'func': str(job_state.get('func')),
        }
        return expected == current

    @classmethod
    def _sync_update_job(
        cls, job_id: str, job_info: JobModel | None, scheduler_job: Job | None, job_update_time: datetime | None
    ) -> None:
        """
        同步更新任务配置

        :param job_id: 任务ID
        :param job_info: 数据库任务对象
        :param scheduler_job: 调度器任务对象
        :param job_update_time: 任务更新时间
        :return: None
        """
        if not job_info or not scheduler_job:
            return
        if cls._should_skip_job_update(job_id, job_update_time):
            return
        if not cls._is_job_config_in_sync(scheduler_job, job_info):
            scheduler.remove_job(job_id=job_id)
            cls._add_job_to_scheduler(job_info)
            logger.info(f'♻️ 同步更新任务: {job_info.job_name}')
        cls._refresh_job_update_cache(job_id, job_update_time)

    @classmethod
    def _should_skip_job_update(cls, job_id: str, job_update_time: datetime | None) -> bool:
        """
        判断是否跳过同步更新

        :param job_id: 任务ID
        :param job_update_time: 任务更新时间
        :return: 是否跳过
        """
        if job_update_time is None:
            return False
        return cls._job_update_time_cache.get(job_id) == job_update_time

    @classmethod
    def _refresh_job_update_cache(cls, job_id: str, job_update_time: datetime | None) -> None:
        """
        刷新任务更新时间缓存

        :param job_id: 任务ID
        :param job_update_time: 任务更新时间
        :return: None
        """
        if job_update_time is not None:
            cls._job_update_time_cache[job_id] = job_update_time
        else:
            cls._job_update_time_cache.pop(job_id, None)

    @classmethod
    async def request_scheduler_sync(cls) -> None:
        """
        请求调度器同步任务状态

        :return: None
        """
        if cls._is_leader:
            cls._sync_pending = True
            cls._ensure_sync_task()
            return
        if cls._redis:
            await cls._redis.publish(cls._sync_channel, cls._worker_id)

    @classmethod
    def _ensure_sync_task(cls) -> None:
        """
        启动同步调度任务

        :return: None
        """
        if cls._sync_task and not cls._sync_task.done():
            return
        cls._sync_task = asyncio.create_task(cls._run_sync_loop())

    @classmethod
    def _get_sync_async_session(cls) -> Any:
        """
        获取同步任务使用的异步 Session

        :return: 异步 Session
        """
        if not cls._sync_async_sessionmaker:
            cls._sync_async_engine = create_async_db_engine(echo=False)
            cls._sync_async_sessionmaker = create_async_session_local(cls._sync_async_engine)
        return cls._sync_async_sessionmaker()

    @classmethod
    async def _dispose_sync_async_engine(cls) -> None:
        """
        释放同步任务使用的异步 Engine

        :return: None
        """
        if cls._sync_async_engine:
            await cls._sync_async_engine.dispose()
            cls._sync_async_engine = None
            cls._sync_async_sessionmaker = None

    @classmethod
    def _dispose_sync_engines(cls) -> None:
        """
        释放 Scheduler 使用的同步 Engine

        :return: None
        """
        if cls._disposed_sync_engines:
            return
        if cls._jobstore_engine:
            cls._jobstore_engine.dispose()
            cls._jobstore_engine = None
        if cls._listener_engine:
            cls._listener_engine.dispose()
            cls._listener_engine = None
        cls._session_local = None
        cls._disposed_sync_engines = True

    @classmethod
    def _ensure_reacquire_task(cls) -> None:
        """
        启动锁重新竞争任务

        :return: None
        """
        if not cls._redis:
            return
        if cls._reacquire_task and not cls._reacquire_task.done():
            return
        cls._reacquire_task = asyncio.create_task(cls._run_reacquire_loop())

    @classmethod
    async def _run_reacquire_loop(cls) -> None:
        """
        循环尝试重新获取锁并恢复调度器

        :return: None
        """
        try:
            while not cls._is_leader:
                if not cls._redis:
                    await asyncio.sleep(cls._reacquire_interval_seconds)
                    continue
                acquired = await StartupUtil.acquire_startup_log_gate(
                    redis=cls._redis,
                    lock_key=LockConstant.APP_STARTUP_LOCK_KEY,
                    worker_id=cls._worker_id,
                    lock_expire_seconds=LockConstant.LOCK_EXPIRE_SECONDS,
                )
                if acquired:
                    # 直接调用 _start_scheduler_as_leader，避免重复获取锁
                    await cls._start_scheduler_as_leader(cls._redis)
                    return
                await asyncio.sleep(cls._reacquire_interval_seconds)
        except asyncio.CancelledError:
            raise
        finally:
            cls._reacquire_task = None

    @classmethod
    async def _run_sync_loop(cls) -> None:
        """
        执行同步调度循环

        :return: None
        """
        try:
            while True:
                if not cls._sync_pending:
                    break
                cls._sync_pending = False
                await asyncio.sleep(cls._sync_debounce_seconds)
                await cls._sync_with_throttle()
        except asyncio.CancelledError:
            raise
        finally:
            cls._sync_task = None

    @classmethod
    async def _sync_with_throttle(cls) -> None:
        """
        按节流规则执行同步

        :return: None
        """
        async with cls._sync_lock:
            if not cls._is_leader:
                return
            if cls._last_sync_at:
                elapsed = datetime.now() - cls._last_sync_at
                min_interval = timedelta(seconds=cls._sync_min_interval_seconds)
                if elapsed < min_interval:
                    await asyncio.sleep((min_interval - elapsed).total_seconds())
            await cls._sync_jobs_from_database()
            cls._last_sync_at = datetime.now()

    @classmethod
    async def _listen_sync_channel(cls, redis: aioredis.Redis) -> None:
        """
        监听同步请求通道

        :param redis: Redis连接对象
        :return: None
        """
        while True:
            pubsub = redis.pubsub()
            try:
                await pubsub.subscribe(cls._sync_channel)
                async for message in pubsub.listen():
                    if not cls._is_leader:
                        continue
                    if message.get('type') != 'message':
                        continue
                    await cls.request_scheduler_sync()
            except asyncio.CancelledError:
                await pubsub.unsubscribe(cls._sync_channel)
                await pubsub.close()
                raise
            except Exception as e:
                logger.error(f'❌ Scheduler 同步监听异常: {e}，5秒后重试...')
                await pubsub.close()
                await asyncio.sleep(5)
            finally:
                try:
                    await pubsub.close()
                except Exception:
                    pass

    @classmethod
    async def _execute_async_job_with_log(
        cls, job_func: Callable[..., Any], job_info: JobModel, args: list, kwargs: dict
    ) -> None:
        """
        执行异步任务并记录日志

        :param job_func: 任务函数
        :param job_info: 任务对象信息
        :param args: 位置参数
        :param kwargs: 关键字参数
        :return: None
        """
        status = '0'
        exception_info = ''
        job_executor = job_info.job_executor
        if iscoroutinefunction(job_func):
            job_executor = 'default'
        try:
            await job_func(*args, **kwargs)
        except Exception as e:
            status = '1'
            exception_info = str(e)
            logger.error(f'❌ 异步执行任务 {job_info.job_name} 失败: {e}')
        finally:
            cls._record_job_execution_log(job_info, job_executor, status, exception_info)

    @classmethod
    def _record_job_execution_log(cls, job_info: JobModel, job_executor: str, status: str, exception_info: str) -> None:
        """
        记录任务执行日志（用于非 Leader Worker 直接执行任务时）

        :param job_info: 任务对象信息
        :param job_executor: 任务执行器
        :param status: 执行状态 0-成功 1-失败
        :param exception_info: 异常信息
        :return: None
        """
        try:
            job_args = job_info.job_args if job_info.job_args else ''
            job_kwargs = job_info.job_kwargs if job_info.job_kwargs else '{}'
            job_trigger = str(MyCronTrigger.from_crontab(job_info.cron_expression)) if job_info.cron_expression else ''
            job_message = (
                f'事件类型: DirectExecution(非Leader), 任务ID: {job_info.job_id}, '
                f'任务名称: {job_info.job_name}, 执行于{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
            )
            job_log = JobLogModel(
                jobName=job_info.job_name,
                jobGroup=job_info.job_group,
                jobExecutor=job_executor,
                invokeTarget=job_info.invoke_target,
                jobArgs=job_args,
                jobKwargs=job_kwargs,
                jobTrigger=job_trigger,
                jobMessage=job_message,
                status=status,
                exceptionInfo=exception_info,
                createTime=datetime.now(),
            )
            session = cls._get_session_local()()
            try:
                JobLogService.add_job_log_services(session, job_log)
            finally:
                session.close()
        except Exception as e:
            logger.error(f'❌ 记录任务执行日志失败: {e}')

    @classmethod
    def _prepare_scheduler_job_add(cls, job_info: JobModel) -> dict[str, Any]:
        """
        构建调度器任务参数

        :param job_info: 任务对象信息
        :return: 调度器任务参数
        """
        job_func = cls._import_function(job_info.invoke_target)
        job_executor = job_info.job_executor
        if iscoroutinefunction(job_func):
            job_executor = 'default'
        return {
            'func': job_func,
            'trigger': MyCronTrigger.from_crontab(job_info.cron_expression),
            'args': job_info.job_args.split(',') if job_info.job_args else None,
            'kwargs': json.loads(job_info.job_kwargs) if job_info.job_kwargs else None,
            'id': str(job_info.job_id),
            'name': job_info.job_name,
            'misfire_grace_time': 1000000000000 if job_info.misfire_policy == '3' else None,
            'coalesce': job_info.misfire_policy == '2',
            'max_instances': 3 if job_info.concurrent == '0' else 1,
            'jobstore': job_info.job_group,
            'executor': job_executor,
        }

    @classmethod
    def _add_job_to_scheduler(cls, job_info: JobModel) -> None:
        """
        内部方法：将任务添加到调度器（不检查应用锁状态，仅供内部使用）

        :param job_info: 任务对象信息
        """
        try:
            # 先移除已存在的同ID任务
            existing_job = scheduler.get_job(job_id=str(job_info.job_id))
            if existing_job:
                scheduler.remove_job(job_id=str(job_info.job_id))
            scheduler.add_job(**cls._prepare_scheduler_job_add(job_info))
        except Exception as e:
            logger.error(f'❌ 添加任务 {job_info.job_name} 失败: {e}')

    @classmethod
    async def close_system_scheduler(cls) -> None:
        """
        应用关闭时关闭定时任务

        :return:
        """
        if cls._sync_listener_task:
            cls._sync_listener_task.cancel()
            try:
                await cls._sync_listener_task
            except asyncio.CancelledError:
                pass
            cls._sync_listener_task = None
        if cls._sync_task:
            cls._sync_task.cancel()
            try:
                await cls._sync_task
            except asyncio.CancelledError:
                pass
            cls._sync_task = None
            cls._sync_pending = False
        if cls._reacquire_task:
            cls._reacquire_task.cancel()
            try:
                await cls._reacquire_task
            except asyncio.CancelledError:
                pass
            cls._reacquire_task = None
        await cls._dispose_sync_async_engine()
        cls._dispose_sync_engines()
        if cls._lock_lost_task:
            cls._lock_lost_task.cancel()
            try:
                await cls._lock_lost_task
            except asyncio.CancelledError:
                pass
            cls._lock_lost_task = None
        if getattr(scheduler, 'running', False):
            scheduler.shutdown()
            logger.info('✅️ 关闭定时任务成功')
        # 释放锁
        if cls._redis:
            current_holder = await cls._redis.get(LockConstant.APP_STARTUP_LOCK_KEY)
            if current_holder == cls._worker_id:
                await cls._redis.delete(LockConstant.APP_STARTUP_LOCK_KEY)
                logger.info(f'🔓 Worker {cls._worker_id} 释放 Application 锁')

    @classmethod
    def _import_function(cls, func_path: str) -> Callable[..., Any]:
        """
        动态导入函数

        :param func_path: 函数字符串，如module_task.scheduler_test.job
        :return: 导入的函数对象
        """
        module_path, func_name = func_path.rsplit('.', 1)
        module = importlib.import_module(module_path)
        return getattr(module, func_name)

    @classmethod
    def get_scheduler_job(cls, job_id: str | int) -> Job:
        """
        根据任务id获取任务对象

        :param job_id: 任务id
        :return: 任务对象
        """
        query_job = scheduler.get_job(job_id=str(job_id))

        return query_job

    @classmethod
    def add_scheduler_job(cls, job_info: JobModel) -> None:
        """
        根据输入的任务对象信息添加任务

        :param job_info: 任务对象信息
        :return:
        """
        # 非应用锁 worker 跳过操作（数据库状态是持久化的，持有应用锁时会加载）
        if not cls._is_leader:
            return
        scheduler.add_job(**cls._prepare_scheduler_job_add(job_info))

    @classmethod
    def execute_scheduler_job_once(cls, job_info: JobModel) -> None:
        """
        根据输入的任务对象执行一次任务

        :param job_info: 任务对象信息
        :return:
        """
        job_func = cls._import_function(job_info.invoke_target)
        job_executor = job_info.job_executor
        if iscoroutinefunction(job_func):
            job_executor = 'default'

        # 非应用锁 worker：直接执行函数（不通过 scheduler）
        if not cls._is_leader:
            logger.info(f'📍 当前 Worker 未持有 Application 锁，直接执行任务 {job_info.job_name}')
            args = job_info.job_args.split(',') if job_info.job_args else []
            kwargs = json.loads(job_info.job_kwargs) if job_info.job_kwargs else {}
            status = '0'
            exception_info = ''
            try:
                if iscoroutinefunction(job_func):
                    asyncio.create_task(cls._execute_async_job_with_log(job_func, job_info, args, kwargs))  # noqa: RUF006
                else:
                    job_func(*args, **kwargs)
            except Exception as e:
                status = '1'
                exception_info = str(e)
                logger.error(f'❌ 直接执行任务 {job_info.job_name} 失败: {e}')
            finally:
                # 同步任务记录日志（异步任务在 _execute_async_job_with_log 中记录）
                if not iscoroutinefunction(job_func):
                    cls._record_job_execution_log(job_info, job_executor, status, exception_info)
            return

        # 应用锁 worker：通过 scheduler 执行
        job_trigger = DateTrigger()
        if job_info.status == '0':
            job_trigger = OrTrigger(triggers=[DateTrigger(), MyCronTrigger.from_crontab(job_info.cron_expression)])
        scheduler.add_job(
            func=job_func,
            trigger=job_trigger,
            args=job_info.job_args.split(',') if job_info.job_args else None,
            kwargs=json.loads(job_info.job_kwargs) if job_info.job_kwargs else None,
            id=str(job_info.job_id),
            name=job_info.job_name,
            misfire_grace_time=1000000000000 if job_info.misfire_policy == '3' else None,
            coalesce=job_info.misfire_policy == '2',
            max_instances=3 if job_info.concurrent == '0' else 1,
            jobstore=job_info.job_group,
            executor=job_executor,
        )

    @classmethod
    def remove_scheduler_job(cls, job_id: str | int) -> None:
        """
        根据任务id移除任务

        :param job_id: 任务id
        :return:
        """
        # 非应用锁 worker 跳过操作（数据库状态是持久化的，持有应用锁时会根据状态加载）
        if not cls._is_leader:
            return
        query_job = cls.get_scheduler_job(job_id=job_id)
        if query_job:
            scheduler.remove_job(job_id=str(job_id))

    @classmethod
    def scheduler_event_listener(cls, event: SchedulerEvent) -> None:
        """
        调度器事件监听器，记录任务执行日志
        """
        try:
            # 获取事件类型和任务ID
            event_type = event.__class__.__name__
            # 获取任务执行异常信息
            status = '0'
            exception_info = ''
            if event_type == 'JobExecutionEvent' and event.exception:
                exception_info = str(event.exception)
                status = '1'
            if hasattr(event, 'job_id'):
                job_id = event.job_id
                # 跳过内部系统任务（以 _ 开头的任务ID），不记录日志
                if str(job_id).startswith('_'):
                    return
                query_job = cls.get_scheduler_job(job_id=job_id)
                if query_job:
                    query_job_info = query_job.__getstate__()
                    # 获取任务名称
                    job_name = query_job_info.get('name')
                    # 获取任务组名
                    job_group = query_job._jobstore_alias
                    # 获取任务执行器
                    job_executor = query_job_info.get('executor')
                    # 获取调用目标字符串
                    invoke_target = query_job_info.get('func')
                    # 获取调用函数位置参数（安全处理）
                    args = query_job_info.get('args')
                    job_args = ','.join(str(arg) for arg in args) if args else ''
                    # 获取调用函数关键字参数
                    kwargs = query_job_info.get('kwargs')
                    job_kwargs = json.dumps(kwargs) if kwargs else '{}'
                    # 获取任务触发器
                    job_trigger = str(query_job_info.get('trigger'))
                    # 构造日志消息
                    job_message = f'事件类型: {event_type}, 任务ID: {job_id}, 任务名称: {job_name}, 执行于{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
                    job_log = JobLogModel(
                        jobName=job_name,
                        jobGroup=job_group,
                        jobExecutor=job_executor,
                        invokeTarget=invoke_target,
                        jobArgs=job_args,
                        jobKwargs=job_kwargs,
                        jobTrigger=job_trigger,
                        jobMessage=job_message,
                        status=status,
                        exceptionInfo=exception_info,
                        createTime=datetime.now(),
                    )
                    session = cls._get_session_local()()
                    try:
                        JobLogService.add_job_log_services(session, job_log)
                    finally:
                        session.close()
        except Exception as e:
            logger.error(f'❌ 调度任务事件监听器异常: {e}')
