"""complyform-data-cleanup — daily purge + retention trim Cloud Function.

Trigger: Daily Cloud Scheduler (04:00 UTC).

Seven sequential phases handle cancelled-customer purge, scan history
retention trim, GCS findings cleanup, Postmark events TTL, founder metrics
trim, newsletter content trim, and customer lifecycle cleanup.

Contract: per-customer error isolation — one failure does not abort others.
The ``complyform-prod-corpus`` bucket is NEVER touched.
"""

from __future__ import annotations

import contextlib
import logging
from datetime import UTC, datetime, timedelta
from typing import Any

from backend.ops._shared import cloud_function_wrapper, log_action
from backend.shared.firestore import get_db
from backend.shared.tier_config import TIER_CONFIG

_log = logging.getLogger(__name__)

# Retention months per tier — derived from TIER_CONFIG at import time.
RETENTION_MONTHS: dict[str, int | None] = {
    tier: (
        cfg.get("retention_months")  # type: ignore[misc]
        if isinstance(cfg.get("retention_months"), int)
        else None
    )
    for tier, cfg in TIER_CONFIG.items()
}

# Safety: corpus bucket must never be touched
_CORPUS_BUCKET = "complyform-prod-corpus"
_FINDINGS_BUCKET = "complyform-prod-findings"
_PURGE_GRACE_DAYS = 30
_POSTMARK_TTL_DAYS = 30
_FOUNDER_METRICS_MONTHS = 24
_NEWSLETTER_CONTENT_DAYS = 90
_NEWSLETTER_DRAFTS_MONTHS = 12


@cloud_function_wrapper("complyform-data-cleanup")
async def data_cleanup(request: Any) -> dict[str, Any]:
    """Daily data lifecycle management."""
    db = get_db()
    now = datetime.now(UTC)
    stats: dict[str, int] = {
        "purged_customers": 0,
        "trimmed_scans": 0,
        "gcs_deleted": 0,
        "postmark_deleted": 0,
        "founder_metrics_deleted": 0,
        "newsletter_deleted": 0,
    }

    # ── Phase 1: Cancelled customer purge ──
    cutoff = now - timedelta(days=_PURGE_GRACE_DAYS)
    query = (
        db.collection("customers")
        .where("status", "==", "cancelled")
        .where("subscription_expires_at", "<", cutoff.isoformat())
    )
    async for doc in query.stream():
        customer_id = doc.id
        try:
            await _purge_customer(db, customer_id)
            stats["purged_customers"] += 1
            log_action(customer_id, "purge", record_count=1)
        except Exception as exc:
            log_action(customer_id, "purge", error=str(exc))
            _log.exception("Phase 1 error for %s", customer_id)

    # ── Phase 2: Scan history retention trim ──
    active_query = db.collection("customers").where("status", "==", "active")
    async for doc in active_query.stream():
        customer_id = doc.id
        try:
            data = doc.to_dict() or {}
            tier = data.get("tier", "community")
            months = RETENTION_MONTHS.get(tier)
            if months is None:
                continue
            trimmed = await _trim_scan_history(db, customer_id, now, months)
            if trimmed > 0:
                stats["trimmed_scans"] += trimmed
                log_action(customer_id, "trim", record_count=trimmed)
        except Exception as exc:
            log_action(customer_id, "trim", error=str(exc))
            _log.exception("Phase 2 error for %s", customer_id)

    # ── Phase 3: GCS findings cleanup (done inline in Phase 2) ──
    # GCS cleanup for trimmed scan entries is handled in _trim_scan_history

    # ── Phase 4: Postmark events TTL ──
    try:
        postmark_cutoff = now - timedelta(days=_POSTMARK_TTL_DAYS)
        pm_query = db.collection("postmark_events").where(
            "timestamp", "<", postmark_cutoff.isoformat()
        )
        count = 0
        async for pm_doc in pm_query.stream():
            await pm_doc.reference.delete()
            count += 1
        stats["postmark_deleted"] = count
        if count > 0:
            log_action(None, "postmark_cleanup", record_count=count)
    except Exception as exc:
        log_action(None, "postmark_cleanup", error=str(exc))
        _log.exception("Phase 4 error")

    # ── Phase 5: Founder metrics retention trim ──
    try:
        fm_cutoff = now - timedelta(days=_FOUNDER_METRICS_MONTHS * 30)
        fm_cutoff_str = fm_cutoff.strftime("%Y-%m-%d")
        count = 0
        async for fm_doc in db.collection("founder_metrics").stream():
            doc_id = fm_doc.id
            # Never delete the github_issues singleton
            if doc_id == "github_issues":
                continue
            if doc_id < fm_cutoff_str:
                await fm_doc.reference.delete()
                count += 1
        stats["founder_metrics_deleted"] = count
        if count > 0:
            log_action(None, "founder_metrics_trim", record_count=count)
    except Exception as exc:
        log_action(None, "founder_metrics_trim", error=str(exc))
        _log.exception("Phase 5 error")

    # ── Phase 6: Newsletter content retention ──
    try:
        count = 0
        content_cutoff = (now - timedelta(days=_NEWSLETTER_CONTENT_DAYS)).strftime(
            "%Y-%m-%d"
        )
        async for nc_doc in db.collection("newsletter_content").stream():
            if nc_doc.id < content_cutoff:
                await nc_doc.reference.delete()
                count += 1

        drafts_cutoff = (now - timedelta(days=_NEWSLETTER_DRAFTS_MONTHS * 30)).strftime(
            "%Y-%m-%d"
        )
        async for nd_doc in db.collection("newsletter_drafts").stream():
            if nd_doc.id < drafts_cutoff:
                await nd_doc.reference.delete()
                count += 1

        stats["newsletter_deleted"] = count
        if count > 0:
            log_action(None, "newsletter_trim", record_count=count)
    except Exception as exc:
        log_action(None, "newsletter_trim", error=str(exc))
        _log.exception("Phase 6 error")

    return stats


async def _purge_customer(db: Any, customer_id: str) -> None:
    """Delete all data for a cancelled customer.

    1. Subcollections: projects, scan_history, drift_history, alert_config, consent
    2. GCS findings: gs://complyform-prod-findings/{customer_id}/*
    3. Secret Manager secrets: drift-secret-*, tfc-token-*, webhook-secret-*
    4. customer_lifecycle/{customer_id}
    5. customer_feedback/{customer_id}/* subcollection
    6. Update customer status to "purged"
    """
    customer_ref = db.collection("customers").document(customer_id)

    # 1. Delete subcollections
    subcollections = [
        "projects",
        "scan_history",
        "drift_history",
        "alert_config",
        "consent",
    ]
    project_ids: list[str] = []
    for sub_name in subcollections:
        sub_ref = customer_ref.collection(sub_name)
        async for sub_doc in sub_ref.stream():
            if sub_name == "projects":
                project_ids.append(sub_doc.id)
            await sub_doc.reference.delete()

    # 2. GCS findings cleanup
    await _delete_gcs_prefix(f"{customer_id}/")

    # 3. Secret Manager cleanup
    await _delete_secrets(customer_id, project_ids)

    # 4. customer_lifecycle
    await db.collection("customer_lifecycle").document(customer_id).delete()

    # 5. customer_feedback subcollection
    feedback_ref = db.collection("customer_feedback").document(customer_id)
    async for fb_doc in feedback_ref.collection("entries").stream():
        await fb_doc.reference.delete()
    await feedback_ref.delete()

    # 6. Mark as purged
    await customer_ref.update({"status": "purged"})


async def _trim_scan_history(
    db: Any,
    customer_id: str,
    now: datetime,
    retention_months: int,
) -> int:
    """Delete scan_history entries older than retention period. Returns count."""
    cutoff = now - timedelta(days=retention_months * 30)
    scan_ref = (
        db.collection("customers")
        .document(customer_id)
        .collection("scan_history")
        .where("timestamp", "<", cutoff.isoformat())
    )
    count = 0
    async for scan_doc in scan_ref.stream():
        data = scan_doc.to_dict() or {}
        # Phase 3: delete GCS findings for trimmed scans
        findings_uri = data.get("findings_uri")
        if findings_uri:
            await _delete_gcs_object(findings_uri)
        await scan_doc.reference.delete()
        count += 1
    return count


async def _delete_gcs_prefix(prefix: str) -> None:
    """Delete all objects under a prefix in the findings bucket."""
    try:
        from google.cloud import storage

        client = storage.Client()
        bucket = client.bucket(_FINDINGS_BUCKET)
        blobs = list(bucket.list_blobs(prefix=prefix))
        for blob in blobs:
            blob.delete()
    except Exception:
        _log.exception("GCS prefix delete failed: %s", prefix)


async def _delete_gcs_object(uri: str) -> None:
    """Delete a single GCS object by its gs:// URI."""
    if not uri.startswith("gs://"):
        return
    # Safety: never touch corpus bucket
    if _CORPUS_BUCKET in uri:
        _log.warning("Refusing to delete from corpus bucket: %s", uri)
        return
    try:
        from google.cloud import storage

        path = uri.replace("gs://", "", 1)
        bucket_name, _, blob_name = path.partition("/")
        if not blob_name:
            return
        client = storage.Client()
        bucket = client.bucket(bucket_name)
        bucket.blob(blob_name).delete()
    except Exception:
        _log.exception("GCS object delete failed: %s", uri)


async def _delete_secrets(customer_id: str, project_ids: list[str]) -> None:
    """Delete Secret Manager secrets for a customer and their projects."""
    try:
        from google.cloud import secretmanager

        client = secretmanager.SecretManagerServiceClient()
        project = "complyform-prod"

        secret_names: list[str] = [
            f"projects/{project}/secrets/webhook-secret-{customer_id}",
        ]
        for pid in project_ids:
            secret_names.append(f"projects/{project}/secrets/drift-secret-{pid}")
            secret_names.append(f"projects/{project}/secrets/tfc-token-{pid}")

        for name in secret_names:
            with contextlib.suppress(Exception):
                client.delete_secret(request={"name": name})
    except Exception:
        _log.exception("Secret cleanup failed for %s", customer_id)
