"""General Affairs service — business logic for operators, jadwal, absensi."""

from datetime import date

from sqlalchemy.exc import IntegrityError, SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession

from app.repositories import general_affairs_repository as repo
from app.schemas.general_affairs import JadwalCreate
from app.utils.file_upload import save_upload, UploadContext
from app.utils.image import compress_image  # noqa: F401 (used inline in submit_housekeeping)


async def list_operators(db: AsyncSession, spbu_id: int) -> list[dict]:
    return await repo.get_operators(db, spbu_id)


async def list_jadwal(db: AsyncSession, spbu_id: int, start: date, end: date):
    return await repo.get_jadwal(db, spbu_id, start, end)


async def create_jadwal_bulk(
    db: AsyncSession,
    spbu_id: int,
    items: list[JadwalCreate],
    created_by_id: int,
) -> list:
    # Validate each user is assigned to this SPBU with can_be_scheduled role
    operators = await repo.get_operators(db, spbu_id)
    operator_ids = {op["id"] for op in operators}
    for item in items:
        if item.user_id not in operator_ids:
            raise ValueError(
                f"User {item.user_id} tidak terdaftar sebagai operator di SPBU ini"
            )
    try:
        result = await repo.create_jadwal_bulk(
            db,
            spbu_id,
            [{"user_id": i.user_id, "shift_id": i.shift_id, "tanggal": i.tanggal} for i in items],
            created_by_id,
        )
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data jadwal konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def delete_jadwal(db: AsyncSession, jadwal_id: int, spbu_id: int) -> bool:
    try:
        result = await repo.delete_jadwal(db, jadwal_id, spbu_id)
        await db.commit()
        return result
    except SQLAlchemyError:
        await db.rollback()
        raise


async def list_absensi(db: AsyncSession, spbu_id: int, start: date, end: date):
    return await repo.get_absensi(db, spbu_id, start, end)


async def get_absensi_slot(db: AsyncSession, spbu_id: int, shift_id: int, tanggal: date) -> dict:
    """Return jadwal operators + existing absensi record for a shift+date slot."""
    operators = await repo.get_jadwal_for_slot(db, spbu_id, shift_id, tanggal)
    rows = await repo.get_absensi(db, spbu_id, tanggal, tanggal)
    absensi = next((r for r in rows if r.shift_id == shift_id), None)
    return {"operators": operators, "absensi": absensi}


async def upload_absensi(
    db: AsyncSession,
    spbu_id: int,
    shift_id: int,
    tanggal: date,
    file_bytes: bytes,
    filename: str,
    uploaded_by_id: int,
    foto_eksif_waktu: str | None = None,
) -> object:
    from app.utils.file_upload import delete_file
    # Capture old foto URL before replacing
    existing_rows = await repo.get_absensi(db, spbu_id, tanggal, tanggal)
    old_foto_url = next(
        (r.foto_url for r in existing_rows if r.shift_id == shift_id), None
    )
    compressed = compress_image(file_bytes)
    from app.utils.file_upload import get_spbu_code
    spbu_code = await get_spbu_code(db, spbu_id)
    ctx = UploadContext(spbu_code, "absensi", tanggal)
    foto_url = await save_upload(compressed, filename, ctx)
    try:
        result = await repo.upsert_absensi(
            db, spbu_id, shift_id, tanggal, foto_url, uploaded_by_id, foto_eksif_waktu
        )
        await db.commit()
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data absensi konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise
    await delete_file(old_foto_url)
    return result


async def delete_absensi(db: AsyncSession, spbu_id: int, absensi_id: int) -> None:
    from app.utils.file_upload import delete_file
    absensi = await repo.get_absensi_by_id(db, absensi_id)
    if not absensi or absensi.spbu_id != spbu_id:
        raise ValueError("Absensi tidak ditemukan")
    if absensi.status != "pending":
        raise ValueError("Tidak dapat menghapus data yang sudah disetujui")
    foto_url = absensi.foto_url
    try:
        await repo.hard_delete_absensi(db, absensi_id)
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise
    await delete_file(foto_url)


async def approve_absensi(db: AsyncSession, spbu_id: int, absensi_id: int, reviewed_by_id: int):
    try:
        absensi = await repo.approve_absensi(db, spbu_id, absensi_id, reviewed_by_id)
        if not absensi:
            raise ValueError("Absensi tidak ditemukan")
        await db.commit()
        return absensi
    except ValueError:
        raise
    except SQLAlchemyError:
        await db.rollback()
        raise


async def get_housekeeping_list(db: AsyncSession, spbu_id: int, start: date, end: date):
    return await repo.get_housekeeping(db, spbu_id, start, end)


async def get_housekeeping_slot(db: AsyncSession, spbu_id: int, tanggal: date):
    return await repo.get_housekeeping_slot(db, spbu_id, tanggal)


async def submit_housekeeping(
    db: AsyncSession,
    spbu_id: int,
    tanggal: date,
    uploaded_by_id: int,
    items: list[str],
    files: list[tuple[bytes, str]],
    submitted: bool = False,
) -> object:
    from app.utils.file_upload import delete_file
    # Capture old foto URLs before replacing
    existing_slot = await repo.get_housekeeping_slot(db, spbu_id, tanggal)
    old_foto_urls = [f.foto_url for f in (existing_slot.fotos if existing_slot else [])]

    from app.utils.file_upload import get_spbu_code
    spbu_code = await get_spbu_code(db, spbu_id)
    ctx = UploadContext(spbu_code, "housekeeping", tanggal)

    async def _save(file_bytes: bytes, filename: str) -> str:
        from app.utils.image import compress_image
        compressed = compress_image(file_bytes)
        return await save_upload(compressed, filename, ctx)

    foto_urls = [await _save(b, n) for b, n in files]

    try:
        result = await repo.upsert_housekeeping(
            db, spbu_id, tanggal, uploaded_by_id,
            items, foto_urls, submitted=submitted,
        )
        await db.commit()
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data housekeeping konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise
    for url in old_foto_urls:
        await delete_file(url)
    return result


async def delete_housekeeping(db: AsyncSession, spbu_id: int, hk_id: int) -> None:
    from app.utils.file_upload import delete_file
    hk = await repo.get_housekeeping_by_id(db, hk_id)
    if not hk or hk.spbu_id != spbu_id:
        raise ValueError("Housekeeping tidak ditemukan")
    if hk.status != "pending":
        raise ValueError("Tidak dapat menghapus data yang sudah disetujui")
    foto_urls = [f.foto_url for f in (hk.fotos or [])]
    try:
        await repo.hard_delete_housekeeping(db, hk_id)
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise
    for url in foto_urls:
        await delete_file(url)


async def approve_housekeeping(db: AsyncSession, spbu_id: int, hk_id: int, reviewed_by_id: int):
    try:
        hk = await repo.approve_housekeeping(db, spbu_id, hk_id, reviewed_by_id)
        if not hk:
            raise ValueError("Housekeeping tidak ditemukan")
        await db.commit()
        return hk
    except ValueError:
        raise
    except SQLAlchemyError:
        await db.rollback()
        raise


async def back_to_draft_housekeeping(db: AsyncSession, spbu_id: int, hk_id: int):
    try:
        hk = await repo.back_to_draft_housekeeping(db, spbu_id, hk_id)
        if not hk:
            raise ValueError("Housekeeping tidak ditemukan")
        await db.commit()
        return hk
    except ValueError:
        raise
    except SQLAlchemyError:
        await db.rollback()
        raise


# ── Sapras ────────────────────────────────────────────────────────────────────

async def get_sapras_list(db: AsyncSession, spbu_id: int, start: date, end: date):
    return await repo.get_sapras(db, spbu_id, start, end)


async def get_sapras_slot(db: AsyncSession, spbu_id: int, tanggal: date):
    return await repo.get_sapras_slot(db, spbu_id, tanggal)


async def submit_sapras(
    db: AsyncSession,
    spbu_id: int,
    tanggal: date,
    uploaded_by_id: int,
    catatan: str | None,
    items_files: list[dict],  # [{kegiatan, file_sebelum: (bytes,str)|None, file_sesudah: (bytes,str)|None}]
    submitted: bool = False,
) -> object:
    from app.utils.file_upload import delete_file, get_spbu_code
    from app.utils.image import compress_image

    # Capture old foto URLs before replacing
    existing_slot = await repo.get_sapras_slot(db, spbu_id, tanggal)
    old_urls: list[str] = []
    if existing_slot:
        for item in existing_slot.items:
            if item.foto_sebelum_url:
                old_urls.append(item.foto_sebelum_url)
            if item.foto_sesudah_url:
                old_urls.append(item.foto_sesudah_url)

    spbu_code = await get_spbu_code(db, spbu_id)
    ctx = UploadContext(spbu_code, "sapras", tanggal)

    async def _save(file_bytes: bytes, filename: str) -> str:
        compressed = compress_image(file_bytes)
        return await save_upload(compressed, filename, ctx)

    items_data = []
    for item in items_files:
        foto_sebelum_url = None
        foto_sesudah_url = None
        if item.get("file_sebelum"):
            b, fn = item["file_sebelum"]
            foto_sebelum_url = await _save(b, fn)
        if item.get("file_sesudah"):
            b, fn = item["file_sesudah"]
            foto_sesudah_url = await _save(b, fn)
        items_data.append({
            "kegiatan": item.get("kegiatan", ""),
            "foto_sebelum_url": foto_sebelum_url,
            "foto_sesudah_url": foto_sesudah_url,
        })

    try:
        result = await repo.upsert_sapras(
            db, spbu_id, tanggal, uploaded_by_id,
            catatan, items_data, submitted=submitted,
        )
        await db.commit()
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data sapras konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise
    for url in old_urls:
        await delete_file(url)
    return result


async def back_to_draft_sapras(db: AsyncSession, spbu_id: int, sapras_id: int):
    try:
        sapras = await repo.back_to_draft_sapras(db, spbu_id, sapras_id)
        if not sapras:
            raise ValueError("Sapras tidak ditemukan")
        await db.commit()
        return sapras
    except ValueError:
        raise
    except SQLAlchemyError:
        await db.rollback()
        raise


async def delete_sapras(db: AsyncSession, spbu_id: int, sapras_id: int) -> None:
    from app.utils.file_upload import delete_file
    sapras = await repo.get_sapras_by_id(db, sapras_id)
    if not sapras or sapras.spbu_id != spbu_id:
        raise ValueError("Sapras tidak ditemukan")
    if sapras.status != "pending":
        raise ValueError("Tidak dapat menghapus data yang sudah disubmit")
    old_urls: list[str] = []
    for item in (sapras.items or []):
        if item.foto_sebelum_url:
            old_urls.append(item.foto_sebelum_url)
        if item.foto_sesudah_url:
            old_urls.append(item.foto_sesudah_url)
    try:
        await repo.hard_delete_sapras(db, sapras_id)
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise
    for url in old_urls:
        await delete_file(url)
