"""Nightly job: sync local uploads to Google Drive."""
import asyncio
import logging
import os
from pathlib import Path

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.config import settings

logger = logging.getLogger(__name__)

LOCAL_PREFIX = "/uploads/"


def _parse_local_url(url: str) -> dict | None:
    """
    Parse local URL → dict with keys: env, spbu_code, yyyymmdd, tipe, filename.
    Handles both formats:
      Old: /uploads/{env}/{spbu_code}/{tipe}/{filename}           (4 parts)
      New: /uploads/{env}/{spbu_code}/{yyyymmdd}/{tipe}/{filename} (5 parts)
    Returns None if not a local URL.
    """
    if not url or not url.startswith(LOCAL_PREFIX):
        return None
    parts = url[len(LOCAL_PREFIX):].split("/")
    if len(parts) == 4:
        # Old format — no date folder
        env, spbu_code, tipe, filename = parts
        yyyymmdd = ""
    elif len(parts) >= 5:
        # New format — with date folder
        env, spbu_code, yyyymmdd, tipe = parts[0], parts[1], parts[2], parts[3]
        filename = "/".join(parts[4:])
    else:
        return None
    return {"env": env, "spbu_code": spbu_code, "yyyymmdd": yyyymmdd, "tipe": tipe, "filename": filename}


def _local_path(url: str) -> Path | None:
    p = _parse_local_url(url)
    if not p:
        return None
    base = Path(settings.STORAGE_PATH) / p["env"] / p["spbu_code"]
    if p["yyyymmdd"]:
        return base / p["yyyymmdd"] / p["tipe"] / p["filename"]
    return base / p["tipe"] / p["filename"]


def _do_upload(path: Path, filename: str, spbu_code: str, tipe: str, env: str, yyyymmdd: str) -> str:
    """Read file bytes and upload to GDrive — runs in a thread to avoid blocking the event loop."""
    from app.utils.gdrive import gdrive_upload

    file_bytes = path.read_bytes()
    return gdrive_upload(file_bytes, filename, spbu_code, tipe, env, yyyymmdd)


async def _sync_url(url: str) -> str | None:
    """Upload local file to GDrive. Returns new GDrive URL or None on failure."""
    p = _parse_local_url(url)
    if not p:
        return None
    local_path = _local_path(url)
    if not local_path or not local_path.exists():
        logger.warning("Local file not found: %s", local_path)
        return None
    try:
        # Both read_bytes() and gdrive_upload() are blocking — run together in a thread
        gdrive_url: str = await asyncio.to_thread(
            _do_upload, local_path, p["filename"], p["spbu_code"], p["tipe"], p["env"], p["yyyymmdd"]
        )
        return gdrive_url
    except Exception as e:
        logger.error("GDrive upload failed for %s: %s", url, e)
        return None


async def sync_local_to_gdrive(db: AsyncSession) -> dict:
    """
    Scan all photo/file columns, upload local files to GDrive, update DB URLs.
    Returns stats dict: {synced, failed, skipped}.
    """
    from app.models.expenses import Expense
    from app.models.general_affairs import Absensi, HousekeepingFoto, SaprasItem
    from app.models.operational import (
        LaporanShift,
        LaporanShiftDokumen,
        LaporanShiftIslandFoto,
        StockAdjustmentItemFoto,
    )
    from app.models.penebusan import Penebusan
    from app.models.penerimaan import PenerimaanFoto
    from app.models.penyetoran import Penyetoran
    from app.models.spbu import KontrakSewa

    # (Model, [column_names])
    targets: list[tuple[type, list[str]]] = [
        (KontrakSewa, ["dokumen_url"]),
        (Expense, ["bukti_url"]),
        (LaporanShift, ["source_foto_url"]),
        (LaporanShiftIslandFoto, ["foto_url"]),
        (LaporanShiftDokumen, ["file_url"]),
        (StockAdjustmentItemFoto, ["url"]),
        (PenerimaanFoto, ["url"]),
        (Penyetoran, ["bukti_url"]),
        (Penebusan, ["pdf_do_url", "pdf_bukti_bayar_url"]),
        (Absensi, ["foto_url"]),
        (HousekeepingFoto, ["foto_url"]),
        (SaprasItem, ["foto_sebelum_url", "foto_sesudah_url"]),
    ]

    total_synced = 0
    total_failed = 0
    files_to_delete: list[Path] = []

    for Model, columns in targets:
        for col_name in columns:
            col = getattr(Model, col_name, None)
            if col is None:
                logger.warning("Column %s.%s not found, skipping", Model.__name__, col_name)
                continue

            result = await db.execute(
                select(Model).where(col.like("/uploads/%"))
            )
            rows = result.scalars().all()

            for row in rows:
                old_url: str | None = getattr(row, col_name)
                if not old_url:
                    continue

                gdrive_url = await _sync_url(old_url)
                if gdrive_url:
                    setattr(row, col_name, gdrive_url)
                    db.add(row)

                    # Collect for deletion — only delete after successful commit
                    local_path = _local_path(old_url)
                    if local_path and local_path.exists():
                        files_to_delete.append(local_path)

                    total_synced += 1
                else:
                    total_failed += 1

    await db.commit()

    # Delete local files only after DB commit succeeds — prevents data loss on commit failure
    for path in files_to_delete:
        try:
            os.remove(path)
        except Exception as e:
            logger.warning("Failed to delete local file %s: %s", path, e)

    stats = {"synced": total_synced, "failed": total_failed}
    logger.info("Sync complete: %d synced, %d failed", total_synced, total_failed)
    return stats
