"""Cron job registration — called at FastAPI startup.

Submits requests to CronJobInitiator for each recurring job.
Duplicate registration is safe: CronJob.initiate is idempotent and returns
existing state without error when the job is already scheduled.
"""

import asyncio
import logging

import httpx

from app.cron_jobs import CRON_JOBS

logger = logging.getLogger(__name__)


async def register_cron_jobs(client: httpx.AsyncClient) -> None:
    """Register all recurring cron jobs via CronJobInitiator.

    Idempotent: duplicate registration on restart succeeds without error.
    """
    from app.config import get_settings
    from app.dependencies import restate_auth_headers

    settings = get_settings()
    ingress_url = settings.RESTATE_INGRESS_URL.rstrip("/")
    headers = restate_auth_headers(settings)
    # Query allowed keys from CronJobInitiator to avoid version skew 422s
    allowed_keys: set[str] | None = None
    try:
        resp = await client.get(
            f"{ingress_url}/CronJobInitiator/allowed_keys",
            headers=headers,
            timeout=5.0,
        )
        if resp.is_success and isinstance(resp.json(), list):
            allowed_keys = set(str(k) for k in resp.json())
    except httpx.HTTPError:
        # Endpoint may not exist on older restate_services versions — continue
        allowed_keys = None

    async def _register_one(job: dict) -> None:
        key = job["key"]
        # Use Restate's `/send` (fire-and-forget) endpoint. Cron registration
        # is purely a side-effect: the handler is idempotent (returns
        # `already_existed=true` when state already exists), and the result
        # isn't consumed by anything in the backend. Fire-and-forget removes
        # the cold-start race where N concurrent synchronous POSTs at backend
        # boot exceed the cron handler's warmup window and surface as
        # `httpx.ReadTimeout` errors in Logfire.
        url = f"{ingress_url}/CronJobInitiator/create/send"
        try:
            response = await client.post(url, json=job, headers=headers, timeout=10.0)
            if response.is_success:
                logger.debug("cron_job_register_accepted", extra={"job_id": key})
            else:
                # Treat 422 Unknown cron job key as benign (deployment version skew)
                if response.status_code == 422:
                    logger.debug("cron_job_key_unrecognized", extra={"job_id": key})
                else:
                    logger.warning(
                        "cron_job_register_unexpected",
                        extra={"job_id": key, "status": response.status_code},
                    )
        except Exception as exc:
            # Restate not available at startup — jobs will be registered on
            # the next startup attempt. The runtime workflow continues to
            # operate (cron handlers stay scheduled across restarts).
            logger.warning(
                "cron_job_registration_failed",
                extra={"job_id": key, "error": str(exc)},
            )

    # Fire all three registrations concurrently, don't block startup
    jobs = [j for j in CRON_JOBS if allowed_keys is None or j.get("key") in allowed_keys]
    await asyncio.gather(*[_register_one(job) for job in jobs])
    logger.info("cron_job_registration_complete", extra={"count": len(jobs)})
