from __future__ import annotations

import asyncio
import json
from datetime import datetime, timedelta

from sqlalchemy import delete, select

from app.core.database import SessionLocal
from app.models.knowledge import KnowledgeRetrievalCandidate, KnowledgeRetrievalLog
from app.models.logs import LogRetentionPolicy
from app.services.redis_client import get_sync_redis_client
from app.services.entitlement_service import EntitlementService


class MaintenanceService:
    """Single-runner background maintenance. MySQL remains authoritative."""

    @classmethod
    async def run_forever(cls) -> None:
        while True:
            await asyncio.to_thread(cls.run_due_jobs)
            await asyncio.sleep(3600)

    @classmethod
    def run_due_jobs(cls) -> None:
        redis = get_sync_redis_client()
        lock_acquired = False
        try:
            if redis is not None:
                lock_acquired = bool(redis.set("maintenance:daily:lock", "1", nx=True, ex=3300))
                if not lock_acquired:
                    return
            with SessionLocal() as db:
                EntitlementService.expire_due_entitlements(db)
                db.commit()
                policy = db.scalar(select(LogRetentionPolicy).order_by(LogRetentionPolicy.id).limit(1))
                if not policy or not policy.enabled or not policy.retention_days:
                    return
                if policy.last_run_at and policy.last_run_at.date() >= datetime.now().date():
                    return
                before = datetime.now() - timedelta(days=policy.retention_days)
                deleted = cls.delete_retrieval_logs(db, before)
                policy.last_run_at = datetime.now()
                policy.last_result = json.dumps({"status": "success", "deleted": deleted, "before": before.isoformat()}, ensure_ascii=False)
                db.add(policy)
                db.commit()
        except Exception as exc:
            with SessionLocal() as db:
                policy = db.scalar(select(LogRetentionPolicy).order_by(LogRetentionPolicy.id).limit(1))
                if policy:
                    policy.last_run_at = datetime.now()
                    policy.last_result = json.dumps({"status": "failed", "error": str(exc)[:1000]}, ensure_ascii=False)
                    db.add(policy)
                    db.commit()
        finally:
            if redis is not None and lock_acquired:
                try:
                    redis.delete("maintenance:daily:lock")
                except Exception:
                    pass

    @staticmethod
    def delete_retrieval_logs(db, before: datetime, batch_size: int = 500) -> int:
        total = 0
        while True:
            ids = list(db.scalars(select(KnowledgeRetrievalLog.id).where(KnowledgeRetrievalLog.created_at < before).order_by(KnowledgeRetrievalLog.id).limit(batch_size)).all())
            if not ids:
                return total
            db.execute(delete(KnowledgeRetrievalCandidate).where(KnowledgeRetrievalCandidate.retrieval_log_id.in_(ids)))
            db.execute(delete(KnowledgeRetrievalLog).where(KnowledgeRetrievalLog.id.in_(ids)))
            total += len(ids)
            db.commit()
