"""
Unit tests for server/workflow_engine.py.

Run with: cd server && ../venv/bin/python3 -m unittest tests.test_workflow_engine -v
"""
import json
import os
import sys
import tempfile
import unittest

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))


class WorkflowEngineTests(unittest.TestCase):
    def setUp(self):
        self._tmpdir = tempfile.mkdtemp()
        os.environ["CA_DATA_DIR"] = self._tmpdir
        for mod in ("db", "workflow_engine"):
            sys.modules.pop(mod, None)
        import db as db_mod
        import workflow_engine as wf_mod
        global db, wf
        db, wf = db_mod, wf_mod
        db.init_db()
        self.user_id = db.create_user(1, "Partner One", "partner1@test.com", "testpass123", "partner")

    def _add_rule(self, trigger_event, action_type, action_json=None, condition_json=None, is_active=1):
        return db.execute(
            "INSERT INTO workflow_rule (firm_id, name, trigger_event, condition_json, action_type,"
            " action_json, is_active, created_by) VALUES (?,?,?,?,?,?,?,?)",
            (1, "test rule", trigger_event, json.dumps(condition_json) if condition_json else None,
             action_type, json.dumps(action_json or {}), is_active, self.user_id))

    def test_unknown_trigger_event_raises(self):
        with self.assertRaises(ValueError):
            wf.fire_event(1, "not.a.real.event", {})

    def test_notify_user_action_creates_a_notification(self):
        self._add_rule("task.completed", "notify_user", {"user_id": self.user_id, "body": "done"})
        fired = wf.fire_event(1, "task.completed", {})
        self.assertEqual(fired, 1)
        rows = db.query("SELECT * FROM notification WHERE user_id = ?", (self.user_id,))
        self.assertEqual(len(rows), 1)
        self.assertEqual(rows[0]["body"], "done")

    def test_notify_role_action_notifies_every_matching_active_user(self):
        other = db.create_user(1, "Partner Two", "partner2@test.com", "testpass123", "partner")
        db.create_user(1, "Staffer", "staffer@test.com", "testpass123", "staff")  # wrong role, shouldn't be notified
        self._add_rule("engagement.created", "notify_role", {"role": "partner", "body": "new engagement"})
        fired = wf.fire_event(1, "engagement.created", {})
        self.assertEqual(fired, 1)  # one matching RULE fired, regardless of how many users it notified
        notified_users = {r["user_id"] for r in db.query("SELECT user_id FROM notification")}
        self.assertEqual(notified_users, {self.user_id, other})

    def test_create_task_action_creates_a_real_task_with_title_substitution(self):
        client_id = db.execute("INSERT INTO client (firm_id, code, name) VALUES (1, 'CL-01', 'Test Client')")
        self._add_rule("task.completed", "create_task", {"title": "Follow up on {title}", "priority": "high"})
        wf.fire_event(1, "task.completed", {"title": "Prepare trial balance", "client_id": client_id})
        task = db.query("SELECT * FROM task ORDER BY id DESC LIMIT 1", one=True)
        self.assertEqual(task["title"], "Follow up on Prepare trial balance")
        self.assertEqual(task["priority"], "high")
        self.assertEqual(task["client_id"], client_id)

    def test_inactive_rule_never_fires(self):
        self._add_rule("task.completed", "notify_user", {"user_id": self.user_id}, is_active=0)
        fired = wf.fire_event(1, "task.completed", {})
        self.assertEqual(fired, 0)
        self.assertEqual(db.query("SELECT COUNT(*) n FROM notification", one=True)["n"], 0)

    def test_condition_json_must_match_to_fire(self):
        self._add_rule("engagement.created", "notify_user", {"user_id": self.user_id},
                       condition_json={"type": "statutory_audit"})
        fired_wrong = wf.fire_event(1, "engagement.created", {"type": "tax"})
        self.assertEqual(fired_wrong, 0)
        fired_right = wf.fire_event(1, "engagement.created", {"type": "statutory_audit"})
        self.assertEqual(fired_right, 1)

    def test_rule_scoped_to_a_different_firm_never_fires(self):
        # firm 2 doesn't need to exist for this test — the lookup is a plain
        # WHERE firm_id = ?, no foreign-key row required to prove it's scoped.
        self._add_rule("task.completed", "notify_user", {"user_id": self.user_id})
        fired = wf.fire_event(2, "task.completed", {})  # different firm_id than the rule's
        self.assertEqual(fired, 0)

    def test_malformed_condition_json_fails_open_rather_than_never_firing(self):
        rid = self._add_rule("task.completed", "notify_user", {"user_id": self.user_id})
        db.execute("UPDATE workflow_rule SET condition_json = 'not valid json' WHERE id = ?", (rid,))
        fired = wf.fire_event(1, "task.completed", {})
        self.assertEqual(fired, 1)


if __name__ == "__main__":
    unittest.main()
