"""Audit hook — single entry point for recording auditable events.

Writes a row to `audit.AuditLog` when the app is installed; always emits a
structured log line. Falls back to log-only if the DB write fails so audit
never breaks the calling code.
"""

from __future__ import annotations

from typing import Any

import structlog
from django.apps import apps
from django.db import DatabaseError

from simorgh.core.context import current_request_context

_log = structlog.get_logger("simorgh.audit")


def record_event(
    action: str,
    *,
    resource_type: str,
    resource_id: Any = None,
    before: dict[str, Any] | None = None,
    after: dict[str, Any] | None = None,
    extra: dict[str, Any] | None = None,
    tenant_id: int | None = None,
    organization_node_id: int | None = None,
    actor_id: int | None = None,
    ip_address: str | None = None,
    user_agent: str | None = None,
) -> None:
    """Record an auditable event.

    Explicit kwargs (`tenant_id`, `actor_id`, ...) override values picked up
    from `current_request_context()`. Pass them when you have stale or
    out-of-band data (signals, background jobs, etc.).
    """

    ctx = current_request_context()
    if tenant_id is None and ctx.tenant is not None:
        tenant_id = ctx.tenant.pk
    if actor_id is None and ctx.actor is not None and getattr(ctx.actor, "pk", None):
        actor_id = ctx.actor.pk
    if ip_address is None:
        ip_address = ctx.extra.get("ip_address")
    if user_agent is None:
        user_agent = ctx.extra.get("user_agent", "")

    payload = {
        "action": action,
        "resource_type": resource_type,
        "resource_id": "" if resource_id is None else str(resource_id),
        "tenant_id": tenant_id,
        "organization_node_id": organization_node_id,
        "actor_id": actor_id,
    }
    _log.info("audit.event", **payload, before=before, after=after, extra=extra or {})

    try:
        model = apps.get_model("audit", "AuditLog")
    except LookupError:
        return

    try:
        model.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            actor_id=actor_id,
            action=action,
            resource_type=resource_type,
            resource_id=payload["resource_id"],
            before=before,
            after=after,
            ip_address=ip_address,
            user_agent=user_agent or "",
            extra=extra or {},
        )
    except DatabaseError as exc:  # pragma: no cover — defensive
        _log.warning("audit.persist_failed", error=str(exc), **payload)


def record_service_event(
    action: str,
    *,
    resource: Any = None,
    resource_type: str | None = None,
    resource_id: Any = None,
    before: dict[str, Any] | None = None,
    after: dict[str, Any] | None = None,
    extra: dict[str, Any] | None = None,
) -> None:
    """Convenience wrapper for service-layer audit calls.

    If ``resource`` is a model instance, ``resource_type`` defaults to its
    ``app_label.model`` and ``resource_id`` to its primary key. Tenant /
    organization node are picked up automatically from ``current_request_context()``.
    """

    if resource is not None:
        if resource_type is None:
            meta = getattr(resource, "_meta", None)
            if meta is not None:
                resource_type = f"{meta.app_label}.{meta.model_name}"
        if resource_id is None:
            resource_id = getattr(resource, "pk", None)
    if resource_type is None:
        resource_type = "unknown"

    org_node_id: int | None = None
    if resource is not None:
        org_node_id = getattr(resource, "organization_node_id", None)
    if org_node_id is None:
        ctx = current_request_context()
        if ctx.org_node_ids:
            org_node_id = next(iter(ctx.org_node_ids))

    record_event(
        action,
        resource_type=resource_type,
        resource_id=resource_id,
        before=before,
        after=after,
        extra=extra,
        organization_node_id=org_node_id,
    )


__all__ = ["record_event", "record_service_event"]
