"""Tests for the Automation Rules API (task 3.8).

Coverage
--------
- selectors: get_rules_for_event, list_rules, get_execution_history
- GET/POST  /api/v1/automation/rules/
- GET/PATCH/DELETE  /api/v1/automation/rules/{id}/
- POST  /api/v1/automation/rules/{id}/toggle/
- POST  /api/v1/automation/rules/{id}/run-now/
- GET   /api/v1/automation/rules/{id}/executions/
- GET   /api/v1/automation/templates/
- POST  /api/v1/automation/rules/from-template/{id}/
"""

from __future__ import annotations

import json
import uuid
from unittest.mock import patch

import pytest


# ===========================================================================
# Shared helpers
# ===========================================================================

def _make_user():
    from tests.factories import UserFactory
    return UserFactory()


# ===========================================================================
# Shared fixtures
# ===========================================================================

@pytest.fixture
def tenant_and_node(db):
    from simorgh.apps.organizations.services import create_node
    from simorgh.apps.tenants.models import Tenant

    tenant = Tenant.objects.create(slug=f"ar-{uuid.uuid4().hex[:8]}", name="AR Tenant")
    node = create_node(tenant_id=tenant.pk, name="Root")
    return tenant, node


@pytest.fixture
def make_rule(tenant_and_node):
    tenant, node = tenant_and_node

    def _factory(
        *,
        name: str = "Test Rule",
        trigger_event: str = "crm.lead.created",
        trigger_type: str = "event",
        is_active: bool = True,
        conditions: list | None = None,
        actions: list | None = None,
    ):
        from simorgh.apps.automation.models import AutomationRule

        return AutomationRule.objects.create(
            tenant=tenant,
            organization_node=node,
            name=name,
            trigger_type=trigger_type,
            trigger_event=trigger_event,
            is_active=is_active,
            conditions=conditions or [],
            actions=actions or [],
        )

    return _factory


@pytest.fixture
def make_template(db):
    def _factory(
        *,
        name: str = "Notify on Lead",
        category: str = "crm",
        trigger_event: str = "crm.lead.created",
        trigger_type: str = "event",
        default_conditions: list | None = None,
        default_actions: list | None = None,
        is_system: bool = False,
    ):
        from simorgh.apps.automation.models import AutomationTemplate

        return AutomationTemplate.objects.create(
            name=name,
            category=category,
            trigger_type=trigger_type,
            trigger_event=trigger_event,
            default_conditions=default_conditions or [],
            default_actions=default_actions or [],
            is_system=is_system,
        )

    return _factory


@pytest.fixture
def make_execution(tenant_and_node, make_rule):
    tenant, node = tenant_and_node

    def _factory(*, rule=None, status: str = "success", trigger_event: str = "crm.lead.created"):
        from django.utils import timezone
        from simorgh.apps.automation.models import AutomationExecution

        r = rule or make_rule()
        return AutomationExecution.objects.create(
            tenant=tenant,
            organization_node=node,
            rule=r,
            rule_version=r.version,
            trigger_event=trigger_event,
            idempotency_key=str(uuid.uuid4()),
            status=status,
            started_at=timezone.now(),
        )

    return _factory


# ===========================================================================
# Selectors
# ===========================================================================

class TestSelectors:
    @pytest.mark.django_db
    def test_get_rules_for_event_active_only(self, make_rule, tenant_and_node):
        from simorgh.apps.automation.selectors import get_rules_for_event

        tenant, _ = tenant_and_node
        make_rule(trigger_event="crm.lead.created", is_active=True)
        make_rule(trigger_event="crm.lead.created", is_active=False)

        qs = get_rules_for_event(tenant.pk, "crm.lead.created")
        assert qs.count() == 1

    @pytest.mark.django_db
    def test_get_rules_for_event_wrong_event(self, make_rule, tenant_and_node):
        from simorgh.apps.automation.selectors import get_rules_for_event

        tenant, _ = tenant_and_node
        make_rule(trigger_event="crm.lead.created")

        qs = get_rules_for_event(tenant.pk, "other.event")
        assert qs.count() == 0

    @pytest.mark.django_db
    def test_list_rules_all_types(self, make_rule, tenant_and_node):
        from simorgh.apps.automation.selectors import list_rules

        tenant, _ = tenant_and_node
        make_rule(name="A")
        make_rule(name="B")
        qs = list_rules(tenant.pk)
        assert qs.count() == 2

    @pytest.mark.django_db
    def test_get_execution_history(self, make_execution, make_rule, tenant_and_node):
        from simorgh.apps.automation.selectors import get_execution_history

        tenant, _ = tenant_and_node
        rule = make_rule()
        make_execution(rule=rule)
        make_execution(rule=rule)

        qs = get_execution_history(rule.pk, tenant.pk)
        assert qs.count() == 2


# ===========================================================================
# Rules API
# ===========================================================================

class TestRuleAPI:
    def _patch_perm(self):
        return patch("simorgh.apps.automation.api.views._require_perm")

    @pytest.mark.django_db
    def test_list_empty(self, api_client, tenant_and_node):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        with self._patch_perm():
            resp = api_client.get("/api/v1/automation/rules/", HTTP_X_TENANT=tenant.slug)
        assert resp.status_code == 200
        assert resp.json()["count"] == 0

    @pytest.mark.django_db
    def test_list_returns_rules(self, api_client, tenant_and_node, make_rule):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        make_rule(name="Rule A")
        make_rule(name="Rule B")
        with self._patch_perm():
            resp = api_client.get("/api/v1/automation/rules/", HTTP_X_TENANT=tenant.slug)
        assert resp.status_code == 200
        assert resp.json()["count"] == 2

    @pytest.mark.django_db
    def test_list_filter_by_trigger_type(self, api_client, tenant_and_node, make_rule):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        make_rule(name="Event Rule", trigger_type="event")
        make_rule(name="Manual Rule", trigger_type="manual")
        with self._patch_perm():
            resp = api_client.get(
                "/api/v1/automation/rules/?trigger_type=event",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 200
        assert resp.json()["count"] == 1
        assert resp.json()["results"][0]["name"] == "Event Rule"

    @pytest.mark.django_db
    def test_create_rule(self, api_client, tenant_and_node):
        from simorgh.apps.automation.models import AutomationRule

        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        with self._patch_perm():
            resp = api_client.post(
                "/api/v1/automation/rules/",
                data=json.dumps({
                    "name": "New Rule",
                    "trigger_type": "event",
                    "trigger_event": "crm.lead.created",
                    "conditions": [],
                    "actions": [],
                }),
                content_type="application/json",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 201, resp.content
        body = resp.json()
        assert body["name"] == "New Rule"
        assert body["version"] == 1
        assert AutomationRule.objects.filter(name="New Rule", tenant=tenant).exists()

    @pytest.mark.django_db
    def test_create_rule_missing_name(self, api_client, tenant_and_node):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        with self._patch_perm():
            resp = api_client.post(
                "/api/v1/automation/rules/",
                data=json.dumps({"trigger_event": "x.y"}),
                content_type="application/json",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 400
        assert "name" in resp.json()["error"]

    @pytest.mark.django_db
    def test_get_rule_detail(self, api_client, tenant_and_node, make_rule):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        rule = make_rule(name="Detail Rule")
        with self._patch_perm():
            resp = api_client.get(
                f"/api/v1/automation/rules/{rule.public_id}/",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 200
        assert resp.json()["name"] == "Detail Rule"

    @pytest.mark.django_db
    def test_patch_rule(self, api_client, tenant_and_node, make_rule):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        rule = make_rule(name="Before")
        with self._patch_perm():
            resp = api_client.patch(
                f"/api/v1/automation/rules/{rule.public_id}/",
                data=json.dumps({"name": "After", "is_active": False}),
                content_type="application/json",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 200
        body = resp.json()
        assert body["name"] == "After"
        assert body["is_active"] is False
        assert body["version"] == 2  # incremented

    @pytest.mark.django_db
    def test_delete_rule(self, api_client, tenant_and_node, make_rule):
        from simorgh.apps.automation.models import AutomationRule

        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        rule = make_rule()
        pk = rule.pk
        with self._patch_perm():
            resp = api_client.delete(
                f"/api/v1/automation/rules/{rule.public_id}/",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 204
        assert not AutomationRule.objects.filter(pk=pk).exists()

    @pytest.mark.django_db
    def test_detail_not_found(self, api_client, tenant_and_node):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        with self._patch_perm():
            resp = api_client.get(
                f"/api/v1/automation/rules/{uuid.uuid4()}/",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 404

    @pytest.mark.django_db
    def test_toggle_rule(self, api_client, tenant_and_node, make_rule):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        rule = make_rule(is_active=True)
        with self._patch_perm():
            resp = api_client.post(
                f"/api/v1/automation/rules/{rule.public_id}/toggle/",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 200
        assert resp.json()["is_active"] is False
        # Toggle again
        with self._patch_perm():
            resp2 = api_client.post(
                f"/api/v1/automation/rules/{rule.public_id}/toggle/",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp2.json()["is_active"] is True

    @pytest.mark.django_db
    def test_run_now_dispatches_task(self, api_client, tenant_and_node, make_rule):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        rule = make_rule()
        with self._patch_perm(), \
             patch("simorgh.apps.automation.tasks.execute_automation_rule_task.delay") as mock_delay:
            resp = api_client.post(
                f"/api/v1/automation/rules/{rule.public_id}/run-now/",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 202
        body = resp.json()
        assert body["status"] == "dispatched"
        assert body["rule_id"] == str(rule.public_id)
        mock_delay.assert_called_once()

    @pytest.mark.django_db
    def test_executions_empty(self, api_client, tenant_and_node, make_rule):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        rule = make_rule()
        with self._patch_perm():
            resp = api_client.get(
                f"/api/v1/automation/rules/{rule.public_id}/executions/",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 200
        assert resp.json()["count"] == 0

    @pytest.mark.django_db
    def test_executions_returns_history(self, api_client, tenant_and_node, make_rule, make_execution):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        rule = make_rule()
        make_execution(rule=rule, status="success")
        make_execution(rule=rule, status="failed")
        with self._patch_perm():
            resp = api_client.get(
                f"/api/v1/automation/rules/{rule.public_id}/executions/",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 200
        assert resp.json()["count"] == 2


# ===========================================================================
# Templates API
# ===========================================================================

class TestTemplateAPI:
    def _patch_perm(self):
        return patch("simorgh.apps.automation.api.views._require_perm")

    @pytest.mark.django_db
    def test_list_empty(self, api_client, tenant_and_node):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        with self._patch_perm():
            resp = api_client.get("/api/v1/automation/templates/", HTTP_X_TENANT=tenant.slug)
        assert resp.status_code == 200
        assert resp.json()["count"] == 0

    @pytest.mark.django_db
    def test_list_templates(self, api_client, tenant_and_node, make_template):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        make_template(name="Lead Notify", category="crm")
        make_template(name="Ticket Assign", category="helpdesk")
        with self._patch_perm():
            resp = api_client.get("/api/v1/automation/templates/", HTTP_X_TENANT=tenant.slug)
        assert resp.status_code == 200
        assert resp.json()["count"] == 2

    @pytest.mark.django_db
    def test_list_filter_by_category(self, api_client, tenant_and_node, make_template):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        make_template(name="T1", category="crm")
        make_template(name="T2", category="helpdesk")
        with self._patch_perm():
            resp = api_client.get(
                "/api/v1/automation/templates/?category=crm",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 200
        assert resp.json()["count"] == 1
        assert resp.json()["results"][0]["name"] == "T1"

    @pytest.mark.django_db
    def test_create_from_template(self, api_client, tenant_and_node, make_template):
        from simorgh.apps.automation.models import AutomationRule

        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        tmpl = make_template(
            name="CRM Lead Template",
            trigger_event="crm.lead.created",
            default_conditions=[{"field": "lead.status", "op": "eq", "value": "new"}],
            default_actions=[{"action": "notifications.send_notification", "params": {}}],
        )
        with self._patch_perm():
            resp = api_client.post(
                f"/api/v1/automation/rules/from-template/{tmpl.public_id}/",
                data=json.dumps({"name": "My Lead Rule"}),
                content_type="application/json",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 201, resp.content
        body = resp.json()
        assert body["name"] == "My Lead Rule"
        assert body["trigger_event"] == "crm.lead.created"
        assert len(body["conditions"]) == 1
        assert len(body["actions"]) == 1
        assert AutomationRule.objects.filter(name="My Lead Rule", tenant=tenant).exists()

    @pytest.mark.django_db
    def test_from_template_uses_template_name_if_not_provided(self, api_client, tenant_and_node, make_template):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        tmpl = make_template(name="Default Name Template")
        with self._patch_perm():
            resp = api_client.post(
                f"/api/v1/automation/rules/from-template/{tmpl.public_id}/",
                data=json.dumps({}),
                content_type="application/json",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 201
        assert resp.json()["name"] == "Default Name Template"

    @pytest.mark.django_db
    def test_from_template_not_found(self, api_client, tenant_and_node):
        tenant, _ = tenant_and_node
        user = _make_user()
        api_client.force_login(user)
        with self._patch_perm():
            resp = api_client.post(
                f"/api/v1/automation/rules/from-template/{uuid.uuid4()}/",
                data=json.dumps({}),
                content_type="application/json",
                HTTP_X_TENANT=tenant.slug,
            )
        assert resp.status_code == 404
