from __future__ import annotations

import logging
import signal
import time
from typing import TYPE_CHECKING

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger

from supply_infra.config import get_infra_settings
from supply_infra.scheduler.constants import SUPPLY_PIPELINE_JOB_ID, SUPPLY_PIPELINE_JOB_NAME
from supply_infra.scheduler.jobs.run_supply_pipeline import run_supply_pipeline

if TYPE_CHECKING:
    from apscheduler.schedulers.base import BaseScheduler

logger = logging.getLogger(__name__)

_scheduler: BackgroundScheduler | None = None

_PIPELINE_CRON_HOUR = 12


def create_scheduler() -> BackgroundScheduler:
    """Create and configure the scheduler with the chained supply pipeline job."""
    settings = get_infra_settings()
    scheduler = BackgroundScheduler(timezone=settings.scheduler_timezone)

    # 每天 12:00 串行执行：全局树 → 需求池 → 分级
    scheduler.add_job(
        run_supply_pipeline,
        trigger=CronTrigger(hour=_PIPELINE_CRON_HOUR, minute=0),
        id=SUPPLY_PIPELINE_JOB_ID,
        name=SUPPLY_PIPELINE_JOB_NAME,
        replace_existing=True,
        max_instances=1,
        coalesce=True,
        misfire_grace_time=3600,
    )

    logger.info("Scheduler configured with %d job(s)", len(scheduler.get_jobs()))
    return scheduler


def _log_next_runs(scheduler: BaseScheduler) -> None:
    for job in scheduler.get_jobs():
        # apscheduler 3.x: 在 scheduler.start() 之前，Job.next_run_time 访问会抛 AttributeError
        # 这里用 getattr 兜底，保证启动阶段不因日志而中断。
        next_run_time = getattr(job, "next_run_time", None)
        logger.info("  - %s | next run: %s", job.name, next_run_time)


def start_scheduler() -> BackgroundScheduler | None:
    """Start the background scheduler (idempotent). Used by API lifespan."""
    global _scheduler

    settings = get_infra_settings()
    if not settings.scheduler_enabled:
        logger.warning("Scheduler is disabled (SCHEDULER_ENABLED=false)")
        return None

    if _scheduler is not None and _scheduler.running:
        return _scheduler

    _scheduler = create_scheduler()
    logger.info("Starting scheduler...")
    _scheduler.start()
    _log_next_runs(_scheduler)
    return _scheduler


def get_scheduler_status() -> dict:
    """Return scheduler runtime status for health checks."""
    settings = get_infra_settings()
    if not settings.scheduler_enabled:
        return {
            "enabled": False,
            "running": False,
            "timezone": settings.scheduler_timezone,
            "jobs": [],
        }

    if _scheduler is None or not _scheduler.running:
        return {
            "enabled": True,
            "running": False,
            "timezone": settings.scheduler_timezone,
            "jobs": [],
        }

    jobs = []
    for job in _scheduler.get_jobs():
        next_run = getattr(job, "next_run_time", None)
        jobs.append(
            {
                "id": job.id,
                "name": job.name,
                "next_run_time": next_run.isoformat() if next_run else None,
            }
        )

    return {
        "enabled": True,
        "running": True,
        "timezone": settings.scheduler_timezone,
        "jobs": jobs,
    }


def stop_scheduler() -> None:
    """Shut down the background scheduler if running."""
    global _scheduler

    if _scheduler is None or not _scheduler.running:
        return

    logger.info("Stopping scheduler...")
    _scheduler.shutdown(wait=False)
    _scheduler = None
    logger.info("Scheduler stopped.")


def run_scheduler() -> None:
    """Start the scheduler and block until interrupted (CLI entry point)."""
    scheduler = start_scheduler()
    if scheduler is None:
        return

    def _handle_exit(signum: int, _frame: object) -> None:
        logger.info("Received signal %s, shutting down scheduler...", signum)
        stop_scheduler()
        raise SystemExit(0)

    signal.signal(signal.SIGINT, _handle_exit)
    signal.signal(signal.SIGTERM, _handle_exit)

    try:
        while scheduler.running:
            time.sleep(3600)
    except (KeyboardInterrupt, SystemExit):
        stop_scheduler()
        logger.info("Scheduler stopped.")
