"""
Orphan cleanup — runs daily via Celery Beat.

Sweeps two kinds of inconsistency between PostgreSQL and MinIO:
  1. PENDING attachments older than 1 hour → delete the row (and object if any)
  2. Objects in MinIO with no DB row → move to quarantine for 7 days then delete

Run schedule: every day at 03:00 UTC (after nightly backup, before business hours)
"""
from __future__ import annotations

import logging
from datetime import datetime, timezone, timedelta

from celery import shared_task
from sqlalchemy import text

from app.db import sync_engine
from app.storage import storage, settings

logger = logging.getLogger(__name__)

STALE_PENDING_AGE = timedelta(hours=1)


@shared_task(bind=True, max_retries=3)
def cleanup_storage_orphans(self):
    """Reconcile PostgreSQL attachments table with MinIO bucket contents."""
    stats = {"pending_deleted": 0, "orphan_objects_quarantined": 0}

    # ---- Pass 1: expire stale PENDINGs ----
    cutoff = datetime.now(tz=timezone.utc) - STALE_PENDING_AGE
    with sync_engine.begin() as conn:
        rows = conn.execute(
            text(
                """
                SELECT attachment_id, file_path
                  FROM attachments
                 WHERE upload_status = 'PENDING'
                   AND uploaded_at < :cutoff
                """
            ),
            {"cutoff": cutoff},
        ).fetchall()

        for row in rows:
            # Try to delete from MinIO (may not exist)
            try:
                _sync_delete(settings.s3_bucket_attachments, row.file_path)
            except Exception as exc:
                logger.warning("MinIO delete failed for %s: %s", row.file_path, exc)

            conn.execute(
                text("DELETE FROM attachments WHERE attachment_id = :id"),
                {"id": row.attachment_id},
            )
            stats["pending_deleted"] += 1

    # ---- Pass 2: find objects in MinIO with no matching DB row ----
    with sync_engine.begin() as conn:
        db_keys = {
            r.file_path
            for r in conn.execute(text("SELECT file_path FROM attachments"))
        }

    orphan_keys = []
    for key in _sync_list_objects(settings.s3_bucket_attachments):
        if key not in db_keys:
            orphan_keys.append(key)

    # Quarantine orphans — don't delete outright in case it's a race condition
    # with an in-flight upload. The quarantine bucket auto-expires in 7 days.
    for key in orphan_keys:
        try:
            _sync_move(
                settings.s3_bucket_attachments,
                key,
                settings.s3_bucket_quarantine,
                key,
            )
            stats["orphan_objects_quarantined"] += 1
        except Exception as exc:
            logger.exception("Failed to quarantine %s: %s", key, exc)

    logger.info("Orphan cleanup done: %s", stats)
    return stats


# Sync helpers for boto3 inside Celery (aioboto3 is for async FastAPI routes)
def _sync_client():
    import boto3

    return boto3.client(
        "s3",
        endpoint_url=settings.s3_endpoint_url,
        aws_access_key_id=settings.s3_access_key,
        aws_secret_access_key=settings.s3_secret_key,
        region_name=settings.s3_region,
    )


def _sync_delete(bucket, key):
    _sync_client().delete_object(Bucket=bucket, Key=key)


def _sync_list_objects(bucket):
    s3 = _sync_client()
    paginator = s3.get_paginator("list_objects_v2")
    for page in paginator.paginate(Bucket=bucket):
        for obj in page.get("Contents", []):
            yield obj["Key"]


def _sync_move(src_bucket, src_key, dst_bucket, dst_key):
    s3 = _sync_client()
    s3.copy_object(
        Bucket=dst_bucket,
        Key=dst_key,
        CopySource={"Bucket": src_bucket, "Key": src_key},
    )
    s3.delete_object(Bucket=src_bucket, Key=src_key)
