import logging
from datetime import datetime, timezone
from pathlib import Path

from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.interval import IntervalTrigger
from sqlalchemy import select

from app.config import settings
from app.database import async_session
from app.models.user import User
from app.models.user_verification_record import UserVerificationRecord
from app.services import verification_service

log = logging.getLogger("qycw.verification.cleanup")

SCHEDULER_TICK_SECONDS = 24 * 60 * 60
PENDING_REDIRECT_POLL_SECONDS = 10 * 60
BATCH_LIMIT = 200

_scheduler: AsyncIOScheduler | None = None


def _material_path(url: str | None) -> Path | None:
    if not url or not url.startswith(f"{settings.MEDIA_URL}/"):
        return None
    relative = url.removeprefix(f"{settings.MEDIA_URL}/").lstrip("/\\")
    root = settings.MEDIA_ROOT_PATH.resolve()
    path = (root / relative).resolve()
    if root != path and root not in path.parents:
        return None
    return path


async def cleanup_expired_verification_materials() -> dict:
    now = datetime.now(timezone.utc)
    cleaned_records = 0
    deleted_files = 0

    async with async_session() as db:
        result = await db.execute(
            select(UserVerificationRecord)
            .where(
                UserVerificationRecord.material_cleaned.is_(False),
                UserVerificationRecord.material_expires_at.is_not(None),
                UserVerificationRecord.material_expires_at <= now,
            )
            .order_by(UserVerificationRecord.id.asc())
            .limit(BATCH_LIMIT)
        )
        records = list(result.scalars().all())
        for record in records:
            material_urls = [
                record.id_card_front_image,
                record.id_card_back_image,
                record.id_card_face_image,
            ]
            for url in material_urls:
                path = _material_path(url)
                if not path:
                    continue
                try:
                    if path.exists() and path.is_file():
                        path.unlink()
                        deleted_files += 1
                except OSError:
                    log.warning("failed to delete verification material: %s", path)

            user = await db.get(User, record.user_id)
            if user:
                if user.id_card_front_image == record.id_card_front_image:
                    user.id_card_front_image = None
                if user.id_card_back_image == record.id_card_back_image:
                    user.id_card_back_image = None
                if user.id_card_face_image == record.id_card_face_image:
                    user.id_card_face_image = None

            record.id_card_front_image = None
            record.id_card_back_image = None
            record.id_card_face_image = None
            if record.status in {"verified", "rejected"}:
                record.real_name = None
                record.id_card_no = None
                record.id_card_no_hash = None
            record.material_cleaned = True
            cleaned_records += 1

        await db.commit()

    return {"cleaned_records": cleaned_records, "deleted_files": deleted_files}


async def _job_cleanup_expired_verification_materials() -> None:
    result = await cleanup_expired_verification_materials()
    log.debug(
        "verification material cleanup: cleaned_records=%s deleted_files=%s",
        result["cleaned_records"],
        result["deleted_files"],
    )


async def _job_poll_pending_redirect_records() -> None:
    async with async_session() as db:
        result = await verification_service.poll_pending_redirect_records(db)
        await db.commit()
    if result.get("checked") or result.get("failed"):
        log.debug(
            "verification redirect poll: checked=%s updated=%s failed=%s",
            result["checked"],
            result["updated"],
            result["failed"],
        )


def start_verification_cleanup_scheduler() -> None:
    global _scheduler
    if _scheduler is not None:
        return
    scheduler = AsyncIOScheduler(timezone="UTC")
    scheduler.add_job(
        _job_cleanup_expired_verification_materials,
        trigger=IntervalTrigger(seconds=SCHEDULER_TICK_SECONDS),
        id="verification_material_cleanup",
        max_instances=1,
        coalesce=True,
        replace_existing=True,
    )
    scheduler.add_job(
        _job_poll_pending_redirect_records,
        trigger=IntervalTrigger(seconds=PENDING_REDIRECT_POLL_SECONDS),
        id="verification_redirect_poll",
        max_instances=1,
        coalesce=True,
        replace_existing=True,
    )
    scheduler.start()
    _scheduler = scheduler
    log.debug("verification cleanup scheduler started: tick=%ss", SCHEDULER_TICK_SECONDS)


def shutdown_verification_cleanup_scheduler() -> None:
    global _scheduler
    if _scheduler is None:
        return
    _scheduler.shutdown(wait=False)
    _scheduler = None
