"""
Scheduler für automatisches Feed-Fetching.

Verwendet APScheduler für periodische Jobs basierend auf Topic-Konfiguration.
"""
import logging
from datetime import datetime
from typing import Optional

from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.interval import IntervalTrigger
from apscheduler.jobstores.memory import MemoryJobStore
from sqlalchemy.orm import Session

from alerts_agent.db.database import SessionLocal
from alerts_agent.db.repository import TopicRepository
from alerts_agent.ingestion.rss_fetcher import fetch_and_store_feed

logger = logging.getLogger(__name__)

# Globaler Scheduler (Singleton)
_scheduler: Optional[AsyncIOScheduler] = None


def get_scheduler() -> AsyncIOScheduler:
    """Gibt den globalen Scheduler zurück, erstellt ihn bei Bedarf."""
    global _scheduler
    if _scheduler is None:
        _scheduler = AsyncIOScheduler(
            jobstores={"default": MemoryJobStore()},
            job_defaults={
                "coalesce": True,  # Verpasste Jobs zusammenfassen
                "max_instances": 1,  # Nur eine Instanz pro Job
                "misfire_grace_time": 60,  # 60s Toleranz für verpasste Jobs
            },
        )
    return _scheduler


async def fetch_topic_job(topic_id: str, feed_url: str) -> None:
    """
    Job-Funktion für das Fetchen eines einzelnen Topics.

    Wird vom Scheduler aufgerufen.
    """
    db = SessionLocal()
    try:
        logger.info(f"Scheduler: Fetching topic {topic_id}")
        result = await fetch_and_store_feed(
            topic_id=topic_id,
            feed_url=feed_url,
            db=db,
        )
        logger.info(
            f"Scheduler: Topic {topic_id} - {result['new_items']} new, "
            f"{result['duplicates_skipped']} skipped"
        )
    except Exception as e:
        logger.error(f"Scheduler: Error fetching topic {topic_id}: {e}")
        # Fehler im Topic speichern
        repo = TopicRepository(db)
        repo.update(topic_id, last_fetch_error=str(e))
    finally:
        db.close()


def schedule_topic(
    topic_id: str,
    feed_url: str,
    interval_minutes: int = 60,
) -> str:
    """
    Plant einen periodischen Fetch-Job für ein Topic.

    Args:
        topic_id: ID des Topics
        feed_url: URL des RSS-Feeds
        interval_minutes: Intervall in Minuten

    Returns:
        Job-ID für spätere Referenz
    """
    scheduler = get_scheduler()
    job_id = f"fetch_topic_{topic_id}"

    # Existierenden Job entfernen falls vorhanden
    if scheduler.get_job(job_id):
        scheduler.remove_job(job_id)

    # Neuen Job hinzufügen
    scheduler.add_job(
        fetch_topic_job,
        trigger=IntervalTrigger(minutes=interval_minutes),
        id=job_id,
        name=f"Fetch Topic {topic_id}",
        kwargs={"topic_id": topic_id, "feed_url": feed_url},
        replace_existing=True,
    )

    logger.info(f"Scheduled topic {topic_id} every {interval_minutes} minutes")
    return job_id


def unschedule_topic(topic_id: str) -> bool:
    """
    Entfernt den Fetch-Job für ein Topic.

    Args:
        topic_id: ID des Topics

    Returns:
        True wenn Job entfernt wurde, False wenn nicht gefunden
    """
    scheduler = get_scheduler()
    job_id = f"fetch_topic_{topic_id}"

    if scheduler.get_job(job_id):
        scheduler.remove_job(job_id)
        logger.info(f"Unscheduled topic {topic_id}")
        return True
    return False


def reschedule_topic(
    topic_id: str,
    feed_url: str,
    interval_minutes: int,
) -> str:
    """
    Aktualisiert das Intervall für einen Topic-Job.

    Args:
        topic_id: ID des Topics
        feed_url: URL des RSS-Feeds (falls geändert)
        interval_minutes: Neues Intervall

    Returns:
        Job-ID
    """
    return schedule_topic(topic_id, feed_url, interval_minutes)


def sync_scheduler_with_db() -> dict:
    """
    Synchronisiert den Scheduler mit der Datenbank.

    Lädt alle aktiven Topics und plant/entfernt Jobs entsprechend.

    Returns:
        Dict mit scheduled und unscheduled Counts
    """
    db = SessionLocal()
    scheduler = get_scheduler()

    try:
        repo = TopicRepository(db)
        topics = repo.get_all()

        scheduled = 0
        unscheduled = 0

        # Aktuelle Job-IDs sammeln
        expected_job_ids = set()

        for topic in topics:
            job_id = f"fetch_topic_{topic.id}"

            if topic.is_active and topic.feed_url:
                # Topic sollte geplant sein
                expected_job_ids.add(job_id)
                schedule_topic(
                    topic_id=topic.id,
                    feed_url=topic.feed_url,
                    interval_minutes=topic.fetch_interval_minutes,
                )
                scheduled += 1
            else:
                # Topic sollte nicht geplant sein
                if scheduler.get_job(job_id):
                    scheduler.remove_job(job_id)
                    unscheduled += 1

        # Orphan-Jobs entfernen (Topics die gelöscht wurden)
        for job in scheduler.get_jobs():
            if job.id.startswith("fetch_topic_") and job.id not in expected_job_ids:
                scheduler.remove_job(job.id)
                unscheduled += 1
                logger.info(f"Removed orphan job: {job.id}")

        return {"scheduled": scheduled, "unscheduled": unscheduled}
    finally:
        db.close()


def start_scheduler() -> None:
    """
    Startet den Scheduler.

    Sollte beim App-Start aufgerufen werden.
    """
    scheduler = get_scheduler()

    if not scheduler.running:
        scheduler.start()
        logger.info("Alert scheduler started")

        # Initial mit DB synchronisieren
        result = sync_scheduler_with_db()
        logger.info(
            f"Scheduler synced: {result['scheduled']} topics scheduled, "
            f"{result['unscheduled']} removed"
        )


def stop_scheduler() -> None:
    """
    Stoppt den Scheduler.

    Sollte beim App-Shutdown aufgerufen werden.
    """
    scheduler = get_scheduler()

    if scheduler.running:
        scheduler.shutdown(wait=False)
        logger.info("Alert scheduler stopped")


def get_scheduler_status() -> dict:
    """
    Gibt den Status des Schedulers zurück.

    Returns:
        Dict mit running, jobs_count und job_details
    """
    scheduler = get_scheduler()

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

    return {
        "running": scheduler.running,
        "jobs_count": len(jobs),
        "jobs": jobs,
    }


# Convenience-Funktion für Topic-Aktivierung
async def on_topic_activated(topic_id: str, feed_url: str, interval_minutes: int) -> None:
    """Hook für Topic-Aktivierung - plant den Fetch-Job."""
    schedule_topic(topic_id, feed_url, interval_minutes)


async def on_topic_deactivated(topic_id: str) -> None:
    """Hook für Topic-Deaktivierung - entfernt den Fetch-Job."""
    unschedule_topic(topic_id)


async def on_topic_updated(
    topic_id: str,
    feed_url: str,
    interval_minutes: int,
    is_active: bool,
) -> None:
    """Hook für Topic-Update - aktualisiert oder entfernt den Fetch-Job."""
    if is_active and feed_url:
        reschedule_topic(topic_id, feed_url, interval_minutes)
    else:
        unschedule_topic(topic_id)


async def on_topic_deleted(topic_id: str) -> None:
    """Hook für Topic-Löschung - entfernt den Fetch-Job."""
    unschedule_topic(topic_id)
