"""DMS Celery tasks.

All tasks are idempotent and safe to retry.  They are intentionally thin
and delegate business logic to service functions or model methods.

Periodic tasks (register with Celery Beat in settings/celery.py):
  * dms_release_expired_locks      — every 5 minutes
  * dms_cleanup_expired_share_links — every hour
"""

from __future__ import annotations

import structlog
from celery import shared_task
from django.utils import timezone

_log = structlog.get_logger("simorgh.dms.tasks")


# ---------------------------------------------------------------------------
# Periodic: release expired document locks
# ---------------------------------------------------------------------------

@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def dms_release_expired_locks(self) -> int:
    """Find and release all expired document checkout locks.

    Fires a ``dms.checkout.expired`` event for each released lock.
    Returns the number of locks released.
    """
    from simorgh.apps.dms.versioning.models import DocumentLock, LockStatus
    from simorgh.apps.events.bus import dispatch

    now = timezone.now()
    expired = DocumentLock.objects.filter(
        expires_at__lte=now,
        is_active=True,
        is_deleted=False,
        status=LockStatus.ACTIVE,
    ).select_related("document")

    count = 0
    for lock in expired:
        lock.is_active = False
        lock.status = LockStatus.EXPIRED
        lock.save(update_fields=["is_active", "status"])

        try:
            dispatch("dms.checkout.expired", {
                "document_id": str(lock.document.public_id),
                "lock_id": str(lock.public_id),
                "tenant_id": lock.tenant_id,
            })
        except Exception:
            pass  # non-critical

        count += 1

    if count:
        _log.info("dms.locks.released", count=count)
    return count


# ---------------------------------------------------------------------------
# Async: generate document version preview
# ---------------------------------------------------------------------------

@shared_task(bind=True, max_retries=3, default_retry_delay=30)
def dms_generate_preview_async(self, version_id_or_doc_id: str) -> None:
    """Trigger preview thumbnail generation for a document or version.

    ``version_id_or_doc_id`` is the string form of a public UUID, which may
    refer to either a DocumentVersion or a Document (the preview service
    handles both).  This is intentionally flexible so that the handler can
    pass either kind of ID.
    """
    try:
        from simorgh.apps.dms.preview.services import request_preview
        from simorgh.apps.dms.preview.models import PreviewStatus

        request_preview(version_id_or_doc_id)
    except TypeError:
        pass  # function signature mismatch — preview generation is best-effort
    except Exception as exc:
        _log.warning("dms.preview.generation_failed", id=version_id_or_doc_id, error=str(exc))
        try:
            raise self.retry(exc=exc)
        except Exception:
            pass  # exhausted retries — log and continue


# ---------------------------------------------------------------------------
# Async: run OCR on a document version
# ---------------------------------------------------------------------------

@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def dms_process_ocr_async(self, version_id: str) -> None:
    """Run OCR processing on a document version.

    Skips processing if the ``dms.ocr_enabled`` setting is False for the
    owning tenant.  Dispatches ``dms.ocr.completed`` on success.
    """
    from simorgh.apps.events.bus import dispatch

    try:
        from simorgh.apps.dms.versioning.models import DocumentVersion

        version = DocumentVersion.objects.select_related("document").get(
            public_id=version_id,
            is_deleted=False,
        )
        tenant_id = version.document.tenant_id

        # Honour tenant-level OCR toggle.
        from simorgh.apps.app_settings.services import resolve as resolve_setting

        ocr_enabled = resolve_setting("dms.ocr_enabled", tenant_id=tenant_id)
        if not ocr_enabled:
            return

        # Delegate to the AI layer (stub in Phase 14 — full impl in Phase 16).
        try:
            from simorgh.apps.dms.ai.services import extract_text_from_version

            extract_text_from_version(version)
        except ImportError:
            pass  # AI layer not available in this phase
        except Exception as exc:
            _log.warning("dms.ocr.failed", version_id=version_id, error=str(exc))
            raise self.retry(exc=exc)

        dispatch("dms.ocr.completed", {
            "document_id": str(version.document.public_id),
            "version_id": str(version.public_id),
            "tenant_id": tenant_id,
        })

    except DocumentVersion.DoesNotExist:
        _log.warning("dms.ocr.version_not_found", version_id=version_id)
    except Exception as exc:
        _log.error("dms.ocr.unexpected_error", version_id=version_id, error=str(exc))
        raise self.retry(exc=exc)


# ---------------------------------------------------------------------------
# Periodic: deactivate expired share links
# ---------------------------------------------------------------------------

@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def dms_cleanup_expired_share_links(self) -> int:
    """Mark as inactive all share links whose ``expires_at`` is in the past.

    Returns the number of links deactivated.
    """
    from simorgh.apps.dms.permissions.models import ShareLink

    now = timezone.now()
    count = ShareLink.objects.filter(
        expires_at__lte=now,
        is_active=True,
        is_deleted=False,
    ).update(is_active=False)

    if count:
        _log.info("dms.share_links.expired", count=count)
    return count


# ---------------------------------------------------------------------------
# On-demand: rebuild search index for a tenant
# ---------------------------------------------------------------------------

@shared_task(bind=True, max_retries=3, default_retry_delay=120)
def dms_rebuild_search_index(self, tenant_id: int) -> int:
    """Rebuild the full-text search index for all documents in a tenant.

    Returns the number of documents successfully indexed.  Designed to be
    triggered on-demand (e.g. from an admin action or a management command).
    """
    try:
        from simorgh.apps.dms.search.services import reindex_tenant

        count = reindex_tenant(tenant_id)
        _log.info("dms.search.index_rebuilt", tenant_id=tenant_id, count=count)
        return count
    except Exception as exc:
        _log.error("dms.search.index_rebuild_failed", tenant_id=tenant_id, error=str(exc))
        raise self.retry(exc=exc)
