"""The workflow library: drafts, publishing, immutability.

Published specs are immutable — the service refuses updates (and Postgres has a
trigger backstop from migration 0001). Editing means publishing key@version+1.
Items pin the template row they started on; nothing here can touch a live item.
"""

import json
import logging
from pathlib import Path

from sqlalchemy import select

from app.db import get_session_factory
from app.models import WorkflowTemplate, TemplateSet
from app.schemas import SpecError, validate_spec

log = logging.getLogger(__name__)

SEED_DIR = Path(__file__).resolve().parent.parent / "seeds" / "workflows"


class WorkflowError(Exception):
    def __init__(self, message, code="BAD_INPUT"):
        super().__init__(message)
        self.code = code


def latest_published(session, key: str) -> WorkflowTemplate | None:
    return session.execute(
        select(WorkflowTemplate)
        .where(WorkflowTemplate.key == key, WorkflowTemplate.status == "published")
        .order_by(WorkflowTemplate.version.desc())
    ).scalars().first()


def get_template(session, template_id: str) -> WorkflowTemplate | None:
    return session.get(WorkflowTemplate, template_id)


def library(session) -> list[dict]:
    """Latest published version per key, for pickers."""
    rows = session.execute(
        select(WorkflowTemplate).where(WorkflowTemplate.status == "published")
        .order_by(WorkflowTemplate.key, WorkflowTemplate.version.desc())
    ).scalars().all()
    seen, out = set(), []
    for t in rows:
        if t.key in seen:
            continue
        seen.add(t.key)
        out.append({"key": t.key, "version": t.version, "title": t.title,
                    "variables": t.spec.get("variables", {})})
    return out


def publish(session, actor: str, raw_spec: dict) -> WorkflowTemplate:
    """Validate + publish as the next version of spec['key']. The ONLY write
    path to a published row — there is no update."""
    spec = validate_spec(raw_spec)          # raises SpecError with the full list
    prev = session.execute(
        select(WorkflowTemplate).where(WorkflowTemplate.key == spec.key)
        .order_by(WorkflowTemplate.version.desc())
    ).scalars().first()
    version = (prev.version + 1) if prev else 1
    row = WorkflowTemplate(key=spec.key, version=version, title=spec.title,
                           spec=raw_spec, status="published", created_by=actor)
    session.add(row)
    session.flush()
    return row


def sync_seeds(session, actor: str) -> list[dict]:
    """Publish a NEW version of any seed file whose spec has moved on from the
    latest published one.

    This does NOT weaken immutability: no published row is ever touched: a
    changed seed becomes the next version, and live items keep the version they
    were instantiated on until someone deliberately adopts the new one
    (items.adopt_latest_template). Idempotent — an unchanged seed publishes
    nothing."""
    out = []
    for f in sorted(SEED_DIR.glob("*.json")):
        raw = json.loads(f.read_text())
        key = raw.get("key", f.stem)
        cur = latest_published(session, key)
        if cur is not None and cur.spec == raw:
            continue
        row = publish(session, actor, raw)
        out.append({"key": key, "version": row.version})
    return out


def load_seeds() -> None:
    """Idempotent: publish each seed file's spec at v1 IF the key has no
    published version yet. Never re-publishes — a live library is never
    fought (same philosophy as the kit's level seeding)."""
    if not SEED_DIR.is_dir():
        return
    with get_session_factory()() as session:
        for f in sorted(SEED_DIR.glob("*.json")):
            raw = json.loads(f.read_text())
            key = raw.get("key", f.stem)
            if latest_published(session, key):
                continue
            try:
                publish(session, "system-seed", raw)
                log.info("seeded workflow %s v1", key)
            except SpecError as exc:
                # A broken seed must be LOUD, not silent — it is repo code.
                raise RuntimeError(f"seed workflow '{f.name}' is invalid: {exc.errors}")
        session.commit()
