"""Persistent cron scheduler for workflow execution jobs."""

from __future__ import annotations

import logging
import uuid
from datetime import datetime, timezone
from typing import Any

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

from kiessclaw.runtime import workspace_memory
from kiessclaw.usecases.registry import get_usecase
from kiessclaw.workflow.runner import WorkflowResult, WorkflowRunner

logger = logging.getLogger(__name__)


class KiessScheduler:
    """Manage persisted workflow schedules backed by workspace JSON."""

    def __init__(self, config: dict[str, Any], registry: dict[str, Any]):
        """Initialize scheduler runtime and storage references."""
        self.config = config
        self.registry = registry
        self.memory = workspace_memory(config)
        self.runner = WorkflowRunner(config=config, registry=registry)
        timezone_name = str(config.get("app", {}).get("timezone", "UTC"))
        self.scheduler = BackgroundScheduler(timezone=timezone_name)
        self._started = False

    def add_job(
        self,
        usecase_id: str,
        cron: str,
        inputs: dict[str, Any],
        dry_run: bool = True,
        job_id: str | None = None,
    ) -> str:
        """Persist one scheduled workflow job and register if started."""
        _ = get_usecase(usecase_id)
        cron_fields = self._parse_cron(cron)

        jobs = self._load_jobs()
        resolved_job_id = (job_id or f"job_{uuid.uuid4().hex[:10]}").strip()
        if any(item.get("job_id") == resolved_job_id for item in jobs):
            raise ValueError(f"Job ID already exists: {resolved_job_id}")

        record = {
            "job_id": resolved_job_id,
            "usecase_id": usecase_id,
            "cron": cron,
            "inputs": inputs,
            "dry_run": bool(dry_run),
            "enabled": True,
            "last_run": None,
            "last_result": None,
        }
        jobs.append(record)
        self._save_jobs(jobs)
        if self._started:
            self._register_job(record, cron_fields)
        return resolved_job_id

    def remove_job(self, job_id: str) -> bool:
        """Delete one scheduled job from persisted storage and APScheduler."""
        jobs = self._load_jobs()
        before = len(jobs)
        jobs = [item for item in jobs if item.get("job_id") != job_id]
        if len(jobs) == before:
            return False
        self._save_jobs(jobs)
        if self.scheduler.get_job(job_id):
            self.scheduler.remove_job(job_id)
        return True

    def list_jobs(self) -> list[dict[str, Any]]:
        """Return all persisted scheduled jobs."""
        return self._load_jobs()

    def run_now(self, job_id: str) -> WorkflowResult:
        """Execute one scheduled job immediately and persist run metadata."""
        jobs = self._load_jobs()
        record = next((item for item in jobs if item.get("job_id") == job_id), None)
        if record is None:
            raise ValueError(f"Unknown job_id: {job_id}")

        dry_run = bool(record.get("dry_run", True))
        result = self.runner.run(
            usecase_id=str(record.get("usecase_id") or ""),
            inputs=dict(record.get("inputs") or {}),
            dry_run=dry_run,
            auto_enroll=not dry_run,
        )
        record["last_run"] = datetime.now(timezone.utc).isoformat()
        record["last_result"] = "success" if not result.errors else "; ".join(result.errors)
        self._save_jobs(jobs)
        return result

    def start(self) -> None:
        """Start the background APScheduler and register persisted jobs."""
        if self._started:
            return
        for record in self._load_jobs():
            if not bool(record.get("enabled", True)):
                continue
            try:
                cron_fields = self._parse_cron(str(record.get("cron") or ""))
                self._register_job(record, cron_fields)
            except Exception as exc:  # noqa: BLE001
                logger.warning("[KiessScheduler] failed to register job=%s: %s", record.get("job_id"), exc)
        self.scheduler.start()
        self._started = True

    def _execute_job(self, job_id: str) -> None:
        """Run one APScheduler invocation with safe error handling."""
        try:
            self.run_now(job_id)
        except Exception as exc:  # noqa: BLE001
            logger.warning("[KiessScheduler] scheduled run failed for job=%s: %s", job_id, exc)

    def _register_job(self, record: dict[str, Any], cron_fields: dict[str, str]) -> None:
        """Register a persisted job record with APScheduler."""
        job_id = str(record.get("job_id"))
        trigger = CronTrigger(
            minute=cron_fields["minute"],
            hour=cron_fields["hour"],
            day=cron_fields["day"],
            month=cron_fields["month"],
            day_of_week=cron_fields["day_of_week"],
        )
        self.scheduler.add_job(
            self._execute_job,
            trigger=trigger,
            id=job_id,
            kwargs={"job_id": job_id},
            replace_existing=True,
            coalesce=True,
            max_instances=1,
        )

    def _load_jobs(self) -> list[dict[str, Any]]:
        """Load all scheduled jobs from workspace storage."""
        return self.memory.load_data("scheduled_jobs")

    def _save_jobs(self, jobs: list[dict[str, Any]]) -> None:
        """Persist scheduled jobs to workspace storage."""
        self.memory.save_data("scheduled_jobs", jobs)

    def _parse_cron(self, value: str) -> dict[str, str]:
        """Parse a standard 5-field cron expression into trigger args."""
        fields = value.strip().split()
        if len(fields) != 5:
            raise ValueError("Cron must have 5 fields: minute hour day month day_of_week")
        return {
            "minute": fields[0],
            "hour": fields[1],
            "day": fields[2],
            "month": fields[3],
            "day_of_week": fields[4],
        }
