"""
CA Automator — the workflow engine: EVENT -> CONDITION -> ACTION.

Deliberately small and closed, not a speculative generic rule engine: a fixed
set of trigger_event names the app itself fires (below), simple equality
conditions, and two action types (create a task, notify someone). Every
trigger_event listed here actually has a fire_event(...) call site somewhere
in app.py/notify.py — see PROJECT_STATE.md's note about the job_queue "report"
path for why that discipline matters (an engine nothing calls is dead weight,
not a feature).

condition_json, when present, is a plain {field: value} equality filter
against the event's context dict — e.g. {"module_key": "stat_audit"} on an
engagement.created rule only fires for statutory audit engagements. No
operators beyond equality on purpose; anything needing more than that is a
sign the trigger itself should be split into two events, not that this needs
a condition-expression parser.
"""
import json

import db

TRIGGER_EVENTS = {
    "engagement.created",
    "task.completed",
    "task.overdue",
    "deadline.approaching",
}

ACTION_TYPES = {
    "create_task",
    "notify_user",
    "notify_role",
}


def _condition_matches(condition_json, context):
    if not condition_json:
        return True
    try:
        condition = json.loads(condition_json)
    except (TypeError, ValueError):
        return True  # malformed condition fails open rather than silently never firing
    return all(context.get(k) == v for k, v in condition.items())


def _run_action(firm_id, rule, context):
    try:
        action = json.loads(rule["action_json"] or "{}")
    except (TypeError, ValueError):
        action = {}

    if rule["action_type"] == "create_task":
        title = (action.get("title") or "{title}").format(**{k: v for k, v in context.items() if isinstance(v, (str, int, float))})
        db.execute(
            "INSERT INTO task (engagement_id, client_id, title, assignee_id, priority, due_date, status)"
            " VALUES (?,?,?,?,?,?,'open')",
            (context.get("engagement_id"), context.get("client_id"), title[:500],
             action.get("assignee_id") or context.get("assignee_id"),
             action.get("priority", "normal"), action.get("due_date")))

    elif rule["action_type"] == "notify_user":
        user_id = action.get("user_id") or context.get("assignee_id")
        if user_id:
            db.execute(
                "INSERT INTO notification (user_id, kind, title, body, link) VALUES (?,?,?,?,?)",
                (user_id, "workflow", rule["name"], action.get("body") or rule["name"], action.get("link")))

    elif rule["action_type"] == "notify_role":
        role = action.get("role")
        if role:
            for row in db.query("SELECT id FROM app_user WHERE firm_id = ? AND role = ? AND is_active = 1",
                                (firm_id, role)):
                db.execute(
                    "INSERT INTO notification (user_id, kind, title, body, link) VALUES (?,?,?,?,?)",
                    (row["id"], "workflow", rule["name"], action.get("body") or rule["name"], action.get("link")))


def fire_event(firm_id, event_name, context=None):
    """Call this at the real moment something happens (an engagement gets
    created, a task is marked done, the deadline sweep finds an overdue
    task) — never speculatively. context is whatever fields the specific
    event has available; each action decides which of them it needs."""
    if event_name not in TRIGGER_EVENTS:
        raise ValueError(f"Unknown trigger_event {event_name!r} — add it to TRIGGER_EVENTS first")
    context = context or {}
    rules = db.query(
        "SELECT * FROM workflow_rule WHERE firm_id = ? AND trigger_event = ? AND is_active = 1",
        (firm_id, event_name))
    fired = 0
    for rule in rules:
        if _condition_matches(rule["condition_json"], context):
            _run_action(firm_id, rule, context)
            fired += 1
    return fired
