"""Task 3.9 — Helpdesk Automation Migration.

Coverage
--------
- All 4 helpdesk actions registered in the platform Action Registry.
- ``helpdesk.assign_to_user`` handler assigns the ticket.
- ``helpdesk.set_priority`` handler updates ticket priority.
- ``helpdesk.add_tag`` handler attaches an existing tag.
- ``helpdesk.change_status`` handler transitions ticket status.
- Handlers silently skip when ticket_id is absent or ticket does not exist.
- Data-migration forward: converts helpdesk.Automation rows to AutomationRule.
- Data-migration: ``time_elapsed`` trigger is skipped.
- Data-migration backward: removes migrated rows.
- AutomationEngine deprecation notice present in source.
"""

from __future__ import annotations

import uuid

import pytest


# ===========================================================================
# Helpers
# ===========================================================================

def _make_user():
    from tests.factories import UserFactory
    return UserFactory()


def _make_tenant_and_node():
    from simorgh.apps.organizations.services import create_node
    from simorgh.apps.tenants.models import Tenant

    slug = f"hd-{uuid.uuid4().hex[:8]}"
    tenant = Tenant.objects.create(slug=slug, name="HD Tenant")
    node = create_node(tenant_id=tenant.pk, name="Root")
    return tenant, node


def _make_queue(tenant, node):
    from simorgh.apps.helpdesk.models import Queue
    return Queue.objects.create(tenant=tenant, organization_node=node, name="Support")


def _make_ticket(tenant, node, queue, *, status="new", priority="normal"):
    from simorgh.apps.helpdesk.models import Ticket
    return Ticket.objects.create(
        tenant=tenant,
        organization_node=node,
        queue=queue,
        subject="Test ticket",
        status=status,
        priority=priority,
    )


def _action_ctx(tenant_id, ticket_id, *, action_key="", params=None):
    """Build a minimal ``ActionContext`` for handler tests."""
    from simorgh.apps.automation.registry import ActionContext
    return ActionContext(
        tenant_id=tenant_id,
        rule_id=1,
        trigger_event="helpdesk.ticket.created",
        trigger_payload={"ticket_id": ticket_id},
        params=params or {},
    )


# ===========================================================================
# 1. Registration
# ===========================================================================

class TestHelpdeskActionRegistration:
    """Verify all four helpdesk actions appear in the platform registry."""

    def test_all_four_keys_registered(self):
        from simorgh.apps.automation.registry import _registry

        expected_keys = {
            "helpdesk.assign_to_user",
            "helpdesk.set_priority",
            "helpdesk.add_tag",
            "helpdesk.change_status",
        }
        registered_keys = set(_registry.keys())
        assert expected_keys <= registered_keys, (
            f"Missing keys: {expected_keys - registered_keys}"
        )

    def test_action_spec_metadata(self):
        from simorgh.apps.automation.registry import _registry

        spec = _registry["helpdesk.assign_to_user"]
        assert spec.module == "helpdesk"
        assert spec.label == "Assign Ticket to User"
        assert "user_id" in spec.params_schema.get("properties", {})

    def test_all_have_callable_handlers(self):
        from simorgh.apps.automation.registry import _registry

        for key in ("helpdesk.assign_to_user", "helpdesk.set_priority",
                    "helpdesk.add_tag", "helpdesk.change_status"):
            spec = _registry[key]
            assert callable(spec.handler), f"{key} handler is not callable"


# ===========================================================================
# 2. Handler: assign_to_user
# ===========================================================================

@pytest.mark.django_db
class TestHandlerAssignToUser:

    def test_assigns_ticket(self):
        from simorgh.apps.automation.registry import _registry

        tenant, _node = _make_tenant_and_node()
        queue = _make_queue(tenant, _node)
        agent = _make_user()
        ticket = _make_ticket(tenant, _node, queue)

        ctx = _action_ctx(tenant.pk, ticket.pk, params={"user_id": agent.pk})
        _registry["helpdesk.assign_to_user"].handler(ctx)

        ticket.refresh_from_db()
        assert ticket.assigned_to_id == agent.pk

    def test_no_op_when_ticket_missing(self):
        from simorgh.apps.automation.registry import _registry

        tenant, _node = _make_tenant_and_node()
        ctx = _action_ctx(tenant.pk, 99999, params={"user_id": 1})
        # Must not raise.
        _registry["helpdesk.assign_to_user"].handler(ctx)

    def test_no_op_when_ticket_id_absent(self):
        from simorgh.apps.automation.registry import ActionContext
        from simorgh.apps.automation.registry import _registry

        tenant, _node = _make_tenant_and_node()
        ctx = ActionContext(
            tenant_id=tenant.pk,
            rule_id=1,
            trigger_event="helpdesk.ticket.created",
            trigger_payload={},  # no ticket_id
            params={"user_id": 1},
        )
        _registry["helpdesk.assign_to_user"].handler(ctx)


# ===========================================================================
# 3. Handler: set_priority
# ===========================================================================

@pytest.mark.django_db
class TestHandlerSetPriority:

    def test_sets_priority(self):
        from simorgh.apps.automation.registry import _registry

        tenant, _node = _make_tenant_and_node()
        queue = _make_queue(tenant, _node)
        ticket = _make_ticket(tenant, _node, queue, priority="normal")

        ctx = _action_ctx(tenant.pk, ticket.pk, params={"priority": "critical"})
        _registry["helpdesk.set_priority"].handler(ctx)

        ticket.refresh_from_db()
        assert ticket.priority == "critical"

    def test_no_op_when_priority_absent(self):
        from simorgh.apps.automation.registry import _registry

        tenant, _node = _make_tenant_and_node()
        queue = _make_queue(tenant, _node)
        ticket = _make_ticket(tenant, _node, queue, priority="normal")

        ctx = _action_ctx(tenant.pk, ticket.pk, params={})
        _registry["helpdesk.set_priority"].handler(ctx)

        ticket.refresh_from_db()
        assert ticket.priority == "normal"


# ===========================================================================
# 4. Handler: add_tag
# ===========================================================================

@pytest.mark.django_db
class TestHandlerAddTag:

    def test_adds_existing_tag(self):
        from simorgh.apps.helpdesk.models import Tag, TicketTag
        from simorgh.apps.automation.registry import _registry

        tenant, _node = _make_tenant_and_node()
        queue = _make_queue(tenant, _node)
        ticket = _make_ticket(tenant, _node, queue)
        tag = Tag.objects.create(tenant=tenant, name="vip", is_active=True)

        ctx = _action_ctx(tenant.pk, ticket.pk, params={"tag": "vip"})
        _registry["helpdesk.add_tag"].handler(ctx)

        assert TicketTag.objects.filter(ticket=ticket, tag=tag).exists()

    def test_idempotent_double_add(self):
        from simorgh.apps.helpdesk.models import Tag, TicketTag
        from simorgh.apps.automation.registry import _registry

        tenant, _node = _make_tenant_and_node()
        queue = _make_queue(tenant, _node)
        ticket = _make_ticket(tenant, _node, queue)
        Tag.objects.create(tenant=tenant, name="retry", is_active=True)

        ctx = _action_ctx(tenant.pk, ticket.pk, params={"tag": "retry"})
        _registry["helpdesk.add_tag"].handler(ctx)
        _registry["helpdesk.add_tag"].handler(ctx)

        assert TicketTag.objects.filter(ticket=ticket).count() == 1

    def test_no_op_when_tag_not_found(self):
        from simorgh.apps.helpdesk.models import TicketTag
        from simorgh.apps.automation.registry import _registry

        tenant, _node = _make_tenant_and_node()
        queue = _make_queue(tenant, _node)
        ticket = _make_ticket(tenant, _node, queue)

        ctx = _action_ctx(tenant.pk, ticket.pk, params={"tag": "nonexistent"})
        _registry["helpdesk.add_tag"].handler(ctx)

        assert TicketTag.objects.filter(ticket=ticket).count() == 0


# ===========================================================================
# 5. Handler: change_status
# ===========================================================================

@pytest.mark.django_db
class TestHandlerChangeStatus:

    def test_changes_status(self):
        from simorgh.apps.automation.registry import _registry

        tenant, _node = _make_tenant_and_node()
        queue = _make_queue(tenant, _node)
        ticket = _make_ticket(tenant, _node, queue, status="new")

        ctx = _action_ctx(tenant.pk, ticket.pk, params={"status": "in_progress"})
        _registry["helpdesk.change_status"].handler(ctx)

        ticket.refresh_from_db()
        assert ticket.status == "in_progress"

    def test_no_op_when_status_absent(self):
        from simorgh.apps.automation.registry import _registry

        tenant, _node = _make_tenant_and_node()
        queue = _make_queue(tenant, _node)
        ticket = _make_ticket(tenant, _node, queue, status="new")

        ctx = _action_ctx(tenant.pk, ticket.pk, params={})
        _registry["helpdesk.change_status"].handler(ctx)

        ticket.refresh_from_db()
        assert ticket.status == "new"


# ===========================================================================
# 6. Data-migration logic
# ===========================================================================

@pytest.mark.django_db
class TestDataMigrationLogic:
    """Unit-test the migration helper functions directly (no actual migration run)."""

    _mod = None

    @staticmethod
    def _migration_mod():
        import importlib
        return importlib.import_module(
            "simorgh.apps.helpdesk.migrations.0004_migrate_automations"
        )

    class _FakeApps:
        """Thin stand-in for Django's historical ``apps`` passed to RunPython."""
        @staticmethod
        def get_model(app_label, model_name):
            from django.apps import apps as _apps
            return _apps.get_model(app_label, model_name)

    def test_convert_assign_to_user(self):
        mod = self._migration_mod()
        result = mod._convert_action({"type": "assign_to_user", "user_id": 42})
        assert result == {"action": "helpdesk.assign_to_user", "params": {"user_id": 42}}

    def test_convert_set_priority(self):
        mod = self._migration_mod()
        result = mod._convert_action({"type": "set_priority", "priority": "critical"})
        assert result == {"action": "helpdesk.set_priority", "params": {"priority": "critical"}}

    def test_convert_add_tag(self):
        mod = self._migration_mod()
        result = mod._convert_action({"type": "add_tag", "tag": "vip"})
        assert result == {"action": "helpdesk.add_tag", "params": {"tag": "vip"}}

    def test_convert_change_status(self):
        mod = self._migration_mod()
        result = mod._convert_action({"type": "change_status", "status": "solved"})
        assert result == {"action": "helpdesk.change_status", "params": {"status": "solved"}}

    def test_convert_unknown_type_returns_none(self):
        mod = self._migration_mod()
        result = mod._convert_action({"type": "unknown_action"})
        assert result is None

    def test_trigger_map_completeness(self):
        mod = self._migration_mod()
        expected = {
            "ticket_created": "helpdesk.ticket.created",
            "ticket_updated": "helpdesk.ticket.updated",
            "ticket_status_changed": "helpdesk.ticket.status_changed",
            "reply_added": "helpdesk.reply.added",
        }
        assert mod._TRIGGER_EVENT_MAP == expected

    def test_time_elapsed_not_in_trigger_map(self):
        mod = self._migration_mod()
        assert "time_elapsed" not in mod._TRIGGER_EVENT_MAP

    def test_migrate_forward_creates_automation_rules(self):
        from simorgh.apps.helpdesk.models import Automation
        from simorgh.apps.automation.models import AutomationRule
        from simorgh.apps.organizations.services import create_node
        from simorgh.apps.tenants.models import Tenant

        mod = self._migration_mod()

        tenant = Tenant.objects.create(slug=f"mg-{uuid.uuid4().hex[:8]}", name="MG")
        node = create_node(tenant_id=tenant.pk, name="Root")

        before = AutomationRule.objects.count()

        # Create a source Automation row.
        Automation.objects.create(
            tenant=tenant,
            organization_node=node,
            name="Auto Assign VIP",
            trigger="ticket_created",
            conditions=[{"field": "priority", "op": "eq", "value": "critical"}],
            actions=[{"type": "assign_to_user", "user_id": 5}],
            is_active=True,
        )

        mod._migrate_forward(self._FakeApps(), None)

        added = AutomationRule.objects.count() - before
        assert added == 1

        rule = AutomationRule.objects.filter(name="[Migrated] Auto Assign VIP").first()
        assert rule is not None
        assert rule.trigger_event == "helpdesk.ticket.created"
        assert rule.conditions == [{"field": "priority", "op": "eq", "value": "critical"}]
        assert rule.actions == [{"action": "helpdesk.assign_to_user", "params": {"user_id": 5}}]
        assert rule.is_active is True

    def test_migrate_forward_skips_time_elapsed(self):
        from simorgh.apps.helpdesk.models import Automation
        from simorgh.apps.automation.models import AutomationRule
        from simorgh.apps.organizations.services import create_node
        from simorgh.apps.tenants.models import Tenant

        mod = self._migration_mod()

        tenant = Tenant.objects.create(slug=f"mg-{uuid.uuid4().hex[:8]}", name="MG Skip")
        node = create_node(tenant_id=tenant.pk, name="Root")

        before = AutomationRule.objects.count()

        Automation.objects.create(
            tenant=tenant,
            organization_node=node,
            name="Time Elapsed Rule",
            trigger="time_elapsed",
            conditions=[],
            actions=[],
            is_active=True,
        )

        mod._migrate_forward(self._FakeApps(), None)

        assert AutomationRule.objects.count() == before

    def test_migrate_forward_multi_actions(self):
        """All actions in a row are converted."""
        from simorgh.apps.helpdesk.models import Automation
        from simorgh.apps.automation.models import AutomationRule
        from simorgh.apps.organizations.services import create_node
        from simorgh.apps.tenants.models import Tenant

        mod = self._migration_mod()

        tenant = Tenant.objects.create(slug=f"mg-{uuid.uuid4().hex[:8]}", name="MG Multi")
        node = create_node(tenant_id=tenant.pk, name="Root")

        Automation.objects.create(
            tenant=tenant,
            organization_node=node,
            name="Multi Action Rule",
            trigger="reply_added",
            conditions=[],
            actions=[
                {"type": "add_tag", "tag": "replied"},
                {"type": "set_priority", "priority": "high"},
            ],
            is_active=False,
        )

        mod._migrate_forward(self._FakeApps(), None)

        rule = AutomationRule.objects.filter(name="[Migrated] Multi Action Rule").first()
        assert rule is not None
        assert rule.trigger_event == "helpdesk.reply.added"
        assert rule.is_active is False
        assert rule.actions == [
            {"action": "helpdesk.add_tag", "params": {"tag": "replied"}},
            {"action": "helpdesk.set_priority", "params": {"priority": "high"}},
        ]

    def test_migrate_backward_removes_migrated_rows(self):
        from simorgh.apps.automation.models import AutomationRule, TriggerType
        from simorgh.apps.organizations.services import create_node
        from simorgh.apps.tenants.models import Tenant

        mod = self._migration_mod()

        tenant = Tenant.objects.create(slug=f"mg-{uuid.uuid4().hex[:8]}", name="BW")
        node = create_node(tenant_id=tenant.pk, name="Root")

        # Simulated migrated row.
        AutomationRule.objects.create(
            tenant=tenant,
            organization_node=node,
            name="[Migrated] Some Rule",
            trigger_type=TriggerType.EVENT,
            trigger_event="helpdesk.ticket.created",
            conditions=[],
            actions=[],
        )
        # Non-migrated row that should survive backward.
        AutomationRule.objects.create(
            tenant=tenant,
            organization_node=node,
            name="Keep this rule",
            trigger_type=TriggerType.EVENT,
            trigger_event="helpdesk.ticket.created",
            conditions=[],
            actions=[],
        )

        mod._migrate_backward(self._FakeApps(), None)

        assert not AutomationRule.objects.filter(name__startswith="[Migrated] ").exists()
        assert AutomationRule.objects.filter(name="Keep this rule").exists()


# ===========================================================================
# 7. apps.py registers automations
# ===========================================================================

class TestAppsReadyRegistersActions:
    """Smoke-test: HelpdeskConfig.ready() imports automations.py."""

    def test_ready_imports_automations(self):
        # If the module is already imported (app is started), the actions
        # are already in the registry.
        from simorgh.apps.automation.registry import _registry
        keys = set(_registry.keys())
        assert "helpdesk.assign_to_user" in keys
