"""Automation API views.

Full implementation across tasks 3.3.3, 3.7.6, 3.8.1, 3.10.x.
Only the action discovery endpoint is wired now (task 3.3.3 skeleton).
"""

from __future__ import annotations

import uuid

from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
from rest_framework import status as http_status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response

from simorgh.apps.automation.registry import list_actions

# Supported condition operators and their human-readable labels.
_CONDITION_OPERATORS = [
    {"op": "eq",           "label": "equals"},
    {"op": "neq",          "label": "not equals"},
    {"op": "gt",           "label": "greater than"},
    {"op": "lt",           "label": "less than"},
    {"op": "gte",          "label": "greater than or equal"},
    {"op": "lte",          "label": "less than or equal"},
    {"op": "contains",     "label": "contains"},
    {"op": "startswith",   "label": "starts with"},
    {"op": "in",           "label": "in list"},
    {"op": "not_in",       "label": "not in list"},
    {"op": "is_null",      "label": "is empty"},
    {"op": "is_not_null",  "label": "is not empty"},
]

# Operators applicable to numeric / ID fields.
_NUMERIC_OPERATORS = ["eq", "neq", "gt", "lt", "gte", "lte", "in", "not_in", "is_null", "is_not_null"]
# Operators applicable to string fields.
_STRING_OPERATORS  = ["eq", "neq", "contains", "startswith", "in", "not_in", "is_null", "is_not_null"]


# ---------------------------------------------------------------------------
# Shared helpers
# ---------------------------------------------------------------------------

def _tenant(request: Request):
    """Return the tenant for the request or raise ``PermissionDenied``."""
    tenant = getattr(request, "tenant", None)
    if tenant is None:
        raise PermissionDenied("No active tenant.")
    return tenant


def _require_perm(request: Request, codename: str) -> None:
    """Raise ``PermissionDenied`` when the user lacks *codename*."""
    if not request.user.has_perm(codename):
        raise PermissionDenied(codename)


# ---------------------------------------------------------------------------
# Action discovery (task 3.3.3)
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def action_list(request: Request) -> Response:
    """GET /api/v1/automation/actions/

    Returns all registered action specs grouped by module.
    Populated fully in task 3.3.3.
    """
    specs = list_actions()
    data = [
        {
            "key": s.key,
            "label": s.label,
            "module": s.module,
            "params_schema": s.params_schema,
        }
        for s in specs
    ]
    return Response({"results": data, "count": len(data)})


# ---------------------------------------------------------------------------
# Webhook helpers
# ---------------------------------------------------------------------------

def _endpoint_payload(ep) -> dict:
    """Serialise ``WebhookEndpoint`` → dict.  ``secret`` is excluded."""
    return {
        "id": str(ep.public_id),
        "name": ep.name,
        "description": ep.description,
        "url": ep.url,
        "events": ep.events,
        "is_active": ep.is_active,
        "headers": ep.headers,
        "timeout_seconds": ep.timeout_seconds,
        "failure_count": ep.failure_count,
        "last_success_at": ep.last_success_at.isoformat() if ep.last_success_at else None,
        "last_failure_at": ep.last_failure_at.isoformat() if ep.last_failure_at else None,
        "created_at": ep.created_at.isoformat(),
    }


def _delivery_payload(d) -> dict:
    return {
        "id": str(d.public_id),
        "endpoint_id": str(d.endpoint.public_id),
        "event_name": d.event_name,
        "status": d.status,
        "attempt": d.attempt,
        "response_status": d.response_status,
        "response_body": d.response_body,
        "delivered_at": d.delivered_at.isoformat() if d.delivered_at else None,
        "next_retry_at": d.next_retry_at.isoformat() if d.next_retry_at else None,
        "created_at": d.created_at.isoformat(),
    }


def _resolve_endpoint(tenant, endpoint_id: str):
    """Return ``WebhookEndpoint`` by ``public_id`` within *tenant*."""
    from simorgh.apps.automation.models import WebhookEndpoint

    try:
        uid = uuid.UUID(endpoint_id)
    except ValueError:
        from django.http import Http404
        raise Http404

    return get_object_or_404(WebhookEndpoint, public_id=uid, tenant=tenant)


# ---------------------------------------------------------------------------
# Webhook CRUD  (task 3.7.6)
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def webhook_list(request: Request) -> Response:
    """GET/POST /api/v1/automation/webhooks/"""
    from simorgh.apps.automation.models import WebhookEndpoint

    tenant = _tenant(request)

    if request.method == "GET":
        _require_perm(request, "automation.webhooks.view")
        endpoints = WebhookEndpoint.objects.filter(tenant=tenant).order_by("-created_at")
        return Response(
            {"results": [_endpoint_payload(ep) for ep in endpoints], "count": endpoints.count()}
        )

    # POST
    _require_perm(request, "automation.webhooks.create")
    data = request.data
    required = ("name", "url")
    missing = [f for f in required if not data.get(f)]
    if missing:
        return Response(
            {"error": f"Missing required fields: {', '.join(missing)}"},
            status=http_status.HTTP_400_BAD_REQUEST,
        )

    # Resolve org node: use the first node for this tenant.
    from simorgh.apps.organizations.models import OrganizationNode
    org_node = OrganizationNode.objects.filter(tenant=tenant).first()
    if org_node is None:
        return Response(
            {"error": "No organization node found for this tenant."},
            status=http_status.HTTP_400_BAD_REQUEST,
        )

    ep = WebhookEndpoint.objects.create(
        tenant=tenant,
        organization_node=org_node,
        name=data["name"],
        description=data.get("description", ""),
        url=data["url"],
        secret=data.get("secret", ""),
        events=data.get("events", []),
        is_active=data.get("is_active", True),
        headers=data.get("headers", {}),
        timeout_seconds=int(data.get("timeout_seconds", 30)),
    )
    return Response(_endpoint_payload(ep), status=http_status.HTTP_201_CREATED)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def webhook_detail(request: Request, endpoint_id: str) -> Response:
    """GET/PATCH/DELETE /api/v1/automation/webhooks/{id}/"""
    tenant = _tenant(request)
    ep = _resolve_endpoint(tenant, endpoint_id)

    if request.method == "GET":
        _require_perm(request, "automation.webhooks.view")
        return Response(_endpoint_payload(ep))

    if request.method == "PATCH":
        _require_perm(request, "automation.webhooks.update")
        data = request.data
        allowed = ("name", "description", "url", "secret", "events",
                   "is_active", "headers", "timeout_seconds")
        for field in allowed:
            if field in data:
                value = data[field]
                if field == "timeout_seconds":
                    value = int(value)
                setattr(ep, field, value)
        ep.save(update_fields=[f for f in allowed if f in data])
        return Response(_endpoint_payload(ep))

    # DELETE
    _require_perm(request, "automation.webhooks.delete")
    ep.delete()
    return Response(status=http_status.HTTP_204_NO_CONTENT)


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def webhook_deliveries(request: Request, endpoint_id: str) -> Response:
    """GET /api/v1/automation/webhooks/{id}/deliveries/"""
    tenant = _tenant(request)
    _require_perm(request, "automation.webhooks.view")
    ep = _resolve_endpoint(tenant, endpoint_id)
    deliveries = ep.deliveries.order_by("-created_at")[:100]
    return Response(
        {"results": [_delivery_payload(d) for d in deliveries], "count": len(deliveries)}
    )


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def webhook_test(request: Request, endpoint_id: str) -> Response:
    """POST /api/v1/automation/webhooks/{id}/test/

    Sends a synthetic ``webhook.test`` event to the endpoint immediately
    (bypasses the event bus — direct delivery).
    """
    from simorgh.apps.automation.webhook_service import deliver_webhook

    tenant = _tenant(request)
    _require_perm(request, "automation.webhooks.create")
    ep = _resolve_endpoint(tenant, endpoint_id)

    payload = {"test": True, "triggered_by": request.user.pk}
    delivery = deliver_webhook(ep, "webhook.test", payload)
    return Response(_delivery_payload(delivery), status=http_status.HTTP_202_ACCEPTED)


# ---------------------------------------------------------------------------
# Rule helpers  (task 3.8.1)
# ---------------------------------------------------------------------------

def _rule_payload(rule) -> dict:
    """Serialise ``AutomationRule`` → dict."""
    return {
        "id": str(rule.public_id),
        "name": rule.name,
        "description": rule.description,
        "is_active": rule.is_active,
        "trigger_type": rule.trigger_type,
        "trigger_event": rule.trigger_event,
        "trigger_schedule": rule.trigger_schedule,
        "conditions": rule.conditions,
        "actions": rule.actions,
        "run_count": rule.run_count,
        "last_run_at": rule.last_run_at.isoformat() if rule.last_run_at else None,
        "last_status": rule.last_status,
        "version": rule.version,
        "created_at": rule.created_at.isoformat(),
    }


def _execution_payload(ex) -> dict:
    """Serialise ``AutomationExecution`` → dict."""
    return {
        "id": str(ex.public_id),
        "rule_id": str(ex.rule.public_id) if ex.rule else None,
        "rule_version": ex.rule_version,
        "trigger_event": ex.trigger_event,
        "status": ex.status,
        "started_at": ex.started_at.isoformat(),
        "finished_at": ex.finished_at.isoformat() if ex.finished_at else None,
        "duration_seconds": ex.duration_seconds,
        "actions_executed": ex.actions_executed,
        "error_message": ex.error_message,
    }


def _template_payload(t) -> dict:
    """Serialise ``AutomationTemplate`` → dict."""
    return {
        "id": str(t.public_id),
        "name": t.name,
        "description": t.description,
        "category": t.category,
        "trigger_type": t.trigger_type,
        "trigger_event": t.trigger_event,
        "default_conditions": t.default_conditions,
        "default_actions": t.default_actions,
        "is_system": t.is_system,
        "created_at": t.created_at.isoformat(),
    }


def _resolve_rule(tenant, rule_id: str):
    """Return ``AutomationRule`` by ``public_id`` within *tenant*."""
    from simorgh.apps.automation.models import AutomationRule

    try:
        uid = uuid.UUID(rule_id)
    except ValueError:
        from django.http import Http404
        raise Http404

    return get_object_or_404(AutomationRule, public_id=uid, tenant=tenant)


# ---------------------------------------------------------------------------
# Rule CRUD  (task 3.8.1)
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def rule_list(request: Request) -> Response:
    """GET /api/v1/automation/rules/        — list all rules for tenant
    POST /api/v1/automation/rules/       — create a new rule
    """
    from simorgh.apps.automation.models import AutomationRule
    from simorgh.apps.automation.selectors import list_rules
    from simorgh.apps.organizations.models import OrganizationNode

    tenant = _tenant(request)

    if request.method == "GET":
        _require_perm(request, "automation.rules.view")
        trigger_type = request.query_params.get("trigger_type")
        rules = list_rules(tenant.pk)
        if trigger_type:
            rules = rules.filter(trigger_type=trigger_type)
        return Response({"results": [_rule_payload(r) for r in rules], "count": rules.count()})

    # POST
    _require_perm(request, "automation.rules.create")
    data = request.data
    if not data.get("name"):
        return Response(
            {"error": "name is required"},
            status=http_status.HTTP_400_BAD_REQUEST,
        )

    if "conditions" in data:
        err = _validate_conditions(data["conditions"])
        if err:
            return Response({"error": err}, status=http_status.HTTP_400_BAD_REQUEST)

    if "actions" in data:
        err = _validate_actions(data["actions"])
        if err:
            return Response({"error": err}, status=http_status.HTTP_400_BAD_REQUEST)

    org_node = OrganizationNode.objects.filter(tenant=tenant).first()
    if org_node is None:
        return Response(
            {"error": "No organization node found for this tenant."},
            status=http_status.HTTP_400_BAD_REQUEST,
        )

    rule = AutomationRule.objects.create(
        tenant=tenant,
        organization_node=org_node,
        name=data["name"],
        description=data.get("description", ""),
        is_active=data.get("is_active", True),
        trigger_type=data.get("trigger_type", "event"),
        trigger_event=data.get("trigger_event", ""),
        trigger_schedule=data.get("trigger_schedule", ""),
        conditions=data.get("conditions", []),
        actions=data.get("actions", []),
    )
    return Response(_rule_payload(rule), status=http_status.HTTP_201_CREATED)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def rule_detail(request: Request, rule_id: str) -> Response:
    """GET/PATCH/DELETE /api/v1/automation/rules/{id}/"""
    tenant = _tenant(request)
    rule = _resolve_rule(tenant, rule_id)

    if request.method == "GET":
        _require_perm(request, "automation.rules.view")
        return Response(_rule_payload(rule))

    if request.method == "PATCH":
        _require_perm(request, "automation.rules.update")
        data = request.data
        allowed = (
            "name", "description", "is_active", "trigger_type",
            "trigger_event", "trigger_schedule", "conditions", "actions",
        )

        if "conditions" in data:
            err = _validate_conditions(data["conditions"])
            if err:
                return Response({"error": err}, status=http_status.HTTP_400_BAD_REQUEST)

        if "actions" in data:
            err = _validate_actions(data["actions"])
            if err:
                return Response({"error": err}, status=http_status.HTTP_400_BAD_REQUEST)

        changed = [f for f in allowed if f in data]
        for field in changed:
            setattr(rule, field, data[field])
        if changed:
            rule.save()  # model bumps version automatically
        rule.refresh_from_db()
        return Response(_rule_payload(rule))

    # DELETE
    _require_perm(request, "automation.rules.delete")
    rule.delete()
    return Response(status=http_status.HTTP_204_NO_CONTENT)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def rule_toggle(request: Request, rule_id: str) -> Response:
    """POST /api/v1/automation/rules/{id}/toggle/ — flip is_active"""
    tenant = _tenant(request)
    _require_perm(request, "automation.rules.update")
    rule = _resolve_rule(tenant, rule_id)
    rule.is_active = not rule.is_active
    rule.save()
    rule.refresh_from_db()
    return Response(_rule_payload(rule))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def rule_run_now(request: Request, rule_id: str) -> Response:
    """POST /api/v1/automation/rules/{id}/run-now/ — manual dispatch"""
    from simorgh.apps.automation.executor import execute_rule_async

    tenant = _tenant(request)
    _require_perm(request, "automation.rules.execute")
    rule = _resolve_rule(tenant, rule_id)

    payload = request.data if isinstance(request.data, dict) else {}
    execute_rule_async(rule.pk, payload, trigger_event="manual")
    return Response(
        {"status": "dispatched", "rule_id": str(rule.public_id)},
        status=http_status.HTTP_202_ACCEPTED,
    )


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def rule_executions(request: Request, rule_id: str) -> Response:
    """GET /api/v1/automation/rules/{id}/executions/"""
    from simorgh.apps.automation.selectors import get_execution_history

    tenant = _tenant(request)
    _require_perm(request, "automation.rules.view")
    rule = _resolve_rule(tenant, rule_id)
    executions = list(get_execution_history(rule.pk, tenant.pk)[:100])
    return Response(
        {"results": [_execution_payload(ex) for ex in executions], "count": len(executions)}
    )


# ---------------------------------------------------------------------------
# Templates  (task 3.8.1)
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def template_list(request: Request) -> Response:
    """GET /api/v1/automation/templates/"""
    from simorgh.apps.automation.models import AutomationTemplate

    _tenant(request)  # ensure tenant header is present
    templates = AutomationTemplate.objects.all()
    category = request.query_params.get("category")
    if category:
        templates = templates.filter(category=category)
    return Response(
        {"results": [_template_payload(t) for t in templates], "count": templates.count()}
    )


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def rule_from_template(request: Request, template_id: str) -> Response:
    """POST /api/v1/automation/rules/from-template/{template_id}/

    Instantiates a global ``AutomationTemplate`` into a tenant-scoped
    ``AutomationRule``, deep-copying ``default_conditions`` and
    ``default_actions``.  The caller may override ``name`` and
    ``description`` in the request body.
    """
    from simorgh.apps.automation.models import AutomationRule, AutomationTemplate
    from simorgh.apps.organizations.models import OrganizationNode

    tenant = _tenant(request)
    _require_perm(request, "automation.rules.create")

    try:
        uid = uuid.UUID(template_id)
    except ValueError:
        from django.http import Http404
        raise Http404

    template = get_object_or_404(AutomationTemplate, public_id=uid)

    org_node = OrganizationNode.objects.filter(tenant=tenant).first()
    if org_node is None:
        return Response(
            {"error": "No organization node found for this tenant."},
            status=http_status.HTTP_400_BAD_REQUEST,
        )

    data = request.data
    rule = AutomationRule.objects.create(
        tenant=tenant,
        organization_node=org_node,
        name=data.get("name", template.name),
        description=data.get("description", template.description),
        is_active=data.get("is_active", True),
        trigger_type=template.trigger_type,
        trigger_event=template.trigger_event,
        conditions=list(template.default_conditions),
        actions=list(template.default_actions),
    )
    return Response(_rule_payload(rule), status=http_status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# Validation helpers  (task 3.10.3)
# ---------------------------------------------------------------------------

_VALID_OPS = frozenset(o["op"] for o in _CONDITION_OPERATORS)


def _validate_conditions(conditions) -> str | None:
    """Return an error message or ``None`` if valid."""
    if not isinstance(conditions, list):
        return "conditions must be a list"
    for i, cond in enumerate(conditions):
        if not isinstance(cond, dict):
            return f"conditions[{i}] must be an object"
        if "field" not in cond:
            return f"conditions[{i}] missing required key 'field'"
        if "op" not in cond:
            return f"conditions[{i}] missing required key 'op'"
        if cond["op"] not in _VALID_OPS:
            return f"conditions[{i}].op {cond['op']!r} is not a valid operator"
    return None


def _validate_actions(actions) -> str | None:
    """Return an error message or ``None`` if valid."""
    if not isinstance(actions, list):
        return "actions must be a list"
    for i, action in enumerate(actions):
        if not isinstance(action, dict):
            return f"actions[{i}] must be an object"
        if "action" not in action:
            return f"actions[{i}] missing required key 'action'"
        if not isinstance(action.get("params", {}), dict):
            return f"actions[{i}].params must be an object"
    return None


# ---------------------------------------------------------------------------
# Rule Engine UI Schema  (task 3.10.1 + 3.10.2)
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def trigger_events(request: Request) -> Response:
    """GET /api/v1/automation/trigger-events/

    Returns all events registered in the platform Event Bus — these are
    the events that can be used as ``trigger_event`` on an
    ``AutomationRule``.

    Each item includes ``payload_keys`` so the frontend no-code builder
    can populate the condition-field picker without a second request.
    """
    from simorgh.apps.events.bus import registered_events

    _tenant(request)  # ensure tenant header present

    specs = registered_events()
    results = [
        {
            "name": spec.name,
            "description": spec.description,
            "payload_keys": list(spec.payload_keys),
        }
        for spec in sorted(specs.values(), key=lambda s: s.name)
    ]
    return Response({"results": results, "count": len(results)})


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def condition_fields(request: Request, event_name: str) -> Response:
    """GET /api/v1/automation/condition-fields/{event_name}/

    Returns condition fields and their supported operators for a specific
    event.  Fields are derived from the event's ``payload_keys``.

    Each field carries a ``type`` hint (``"number"`` for keys that end in
    ``_id`` or ``_count``, ``"string"`` otherwise) and the corresponding
    operator list.
    """
    from simorgh.apps.events.bus import registered_events

    _tenant(request)

    specs = registered_events()
    spec = specs.get(event_name)
    if spec is None:
        return Response(
            {"error": f"Event {event_name!r} is not registered."},
            status=http_status.HTTP_404_NOT_FOUND,
        )

    op_map = {o["op"]: o["label"] for o in _CONDITION_OPERATORS}

    fields = []
    for key in spec.payload_keys:
        # Infer field type from naming convention.
        is_numeric = key.endswith("_id") or key.endswith("_count") or key in ("id",)
        applicable_ops = _NUMERIC_OPERATORS if is_numeric else _STRING_OPERATORS
        fields.append({
            "key": key,
            "type": "number" if is_numeric else "string",
            "operators": [{"op": op, "label": op_map[op]} for op in applicable_ops],
        })

    return Response({
        "event_name": event_name,
        "fields": fields,
        "all_operators": _CONDITION_OPERATORS,
    })
