"""Durable master taxonomy job queue (fix_all, bootstrap, push_nodes, etc.)."""

from __future__ import annotations

import os
import uuid
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Optional

from sqlalchemy import select
from sqlalchemy.orm import Session

from db.models import MasterTaxonomyJob

STALE_RUNNING_MINUTES = int(os.getenv("MASTER_TAXONOMY_JOB_STALE_MINUTES", "180"))


def _utcnow() -> datetime:
    return datetime.now(timezone.utc)


def job_to_dict(row: MasterTaxonomyJob) -> Dict[str, Any]:
    return {
        "id": row.id,
        "job_type": row.job_type,
        "status": row.status,
        "created_at": row.requested_at.isoformat() if row.requested_at else None,
        "params": row.params or {},
        "result": row.result,
        "error": row.error,
        "started_at": row.started_at.isoformat() if row.started_at else None,
        "finished_at": row.finished_at.isoformat() if row.finished_at else None,
        "progress": row.progress,
        "notes": row.notes,
    }


def enqueue_master_taxonomy_job(
    session: Session,
    job_type: str,
    params: Dict[str, Any],
) -> MasterTaxonomyJob:
    job_id = str(uuid.uuid4())
    row = MasterTaxonomyJob(
        id=job_id,
        job_type=job_type,
        status="queued",
        params=dict(params),
        requested_at=_utcnow(),
    )
    session.add(row)
    session.flush()
    return row


def get_master_taxonomy_job(session: Session, job_id: str) -> Optional[MasterTaxonomyJob]:
    return session.get(MasterTaxonomyJob, job_id)


def update_master_taxonomy_job_progress(
    session: Session,
    job_id: str,
    *,
    progress: Dict[str, Any],
    notes: Optional[str] = None,
) -> None:
    row = session.get(MasterTaxonomyJob, job_id)
    if row is None:
        return
    row.progress = progress
    if notes is not None:
        row.notes = notes
    row.updated_at = _utcnow()
    session.flush()


def claim_next_master_taxonomy_job(
    session: Session,
    *,
    worker_id: Optional[str] = None,
) -> Optional[MasterTaxonomyJob]:
    worker = worker_id or f"taxonomy-worker-{os.getpid()}"
    cutoff = _utcnow() - timedelta(minutes=STALE_RUNNING_MINUTES)
    stale_rows = session.scalars(
        select(MasterTaxonomyJob)
        .where(MasterTaxonomyJob.status == "running")
        .where(MasterTaxonomyJob.locked_at < cutoff)
        .with_for_update(skip_locked=True)
    ).all()
    for row in stale_rows:
        if row.attempts >= row.max_attempts:
            row.status = "failed"
            row.finished_at = _utcnow()
            row.error = "Exhausted attempts after stale running reset"
            row.notes = row.error
        else:
            row.status = "queued"
            row.locked_at = None
            row.locked_by = None

    row = session.scalars(
        select(MasterTaxonomyJob)
        .where(MasterTaxonomyJob.status == "queued")
        .where(MasterTaxonomyJob.attempts < MasterTaxonomyJob.max_attempts)
        .order_by(MasterTaxonomyJob.requested_at, MasterTaxonomyJob.id)
        .limit(1)
        .with_for_update(skip_locked=True)
    ).first()
    if row is None:
        return None
    row.status = "running"
    row.started_at = row.started_at or _utcnow()
    row.locked_at = _utcnow()
    row.locked_by = worker
    row.attempts = (row.attempts or 0) + 1
    session.flush()
    return row


def mark_master_taxonomy_job_completed(
    session: Session,
    job_id: str,
    *,
    result: Dict[str, Any],
) -> None:
    row = session.get(MasterTaxonomyJob, job_id)
    if row is None:
        return
    row.status = "completed"
    row.result = result
    row.progress = {**(row.progress or {}), "stage": "completed", "percent": 100}
    row.finished_at = _utcnow()
    row.locked_at = None
    row.locked_by = None
    row.updated_at = _utcnow()
    session.flush()


def mark_master_taxonomy_job_failed(
    session: Session,
    job_id: str,
    *,
    error: str,
) -> None:
    row = session.get(MasterTaxonomyJob, job_id)
    if row is None:
        return
    row.status = "failed"
    row.error = error
    row.notes = error
    row.finished_at = _utcnow()
    row.locked_at = None
    row.locked_by = None
    row.updated_at = _utcnow()
    session.flush()
