"""Penerimaan service — business logic for BBM receipt module."""

from datetime import datetime, timezone
from decimal import Decimal

from sqlalchemy import select
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.models.penebusan import Penebusan, PenebusanItem, StatusPenebusan
from app.models.penerimaan import Penerimaan, StatusPenerimaan, TipeFotoEnum
from app.models.spbu import Tangki
from app.repositories import penerimaan_repository, penebusan_repository
from app.schemas.penerimaan import (
    PenerimaanCreate,
    PenerimaanFotoResponse,
    PenerimaanItemResponse,
    PenerimaanResponse,
)
from app.utils.file_upload import save_upload, UploadContext
from app.utils.kalibrasi import interpolate_volume


# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------

async def _load_tangki(db: AsyncSession, tangki_id: int, spbu_id: int) -> Tangki:
    result = await db.execute(
        select(Tangki)
        .where(Tangki.id == tangki_id, Tangki.deleted_at.is_(None))
        .options(
            selectinload(Tangki.kalibrasi),
            selectinload(Tangki.produk),
        )
    )
    tangki = result.scalar_one_or_none()
    if tangki is None:
        raise ValueError(f"Tangki id={tangki_id} tidak ditemukan")
    if tangki.spbu_id != spbu_id:
        raise ValueError(f"Tangki id={tangki_id} tidak termasuk SPBU ini")
    return tangki


def _interpolate(tangki: Tangki, height_mm: Decimal) -> Decimal:
    try:
        return interpolate_volume(height_mm, tangki.kalibrasi)
    except ValueError as e:
        raise ValueError(f"Tangki '{tangki.nama}': {e}") from e


def _build_item_response(item) -> PenerimaanItemResponse:
    return PenerimaanItemResponse(
        id=item.id,
        penerimaan_id=item.penerimaan_id,
        penebusan_item_id=item.penebusan_item_id,
        produk_id=item.produk_id,
        produk_nama=item.produk.nama if item.produk else "",
        tangki_id=item.tangki_id,
        tangki_nama=item.tangki.nama if item.tangki else "",
        dipstick_sebelum_mm=item.dipstick_sebelum_mm,
        volume_sebelum=item.volume_sebelum,
        dipstick_sesudah_mm=item.dipstick_sesudah_mm,
        volume_sesudah=item.volume_sesudah,
        volume_diterima=item.volume_diterima,
        atg_sebelum_mm=item.atg_sebelum_mm,
        atg_sesudah_mm=item.atg_sesudah_mm,
        fotos=[
            PenerimaanFotoResponse(
                id=f.id,
                penerimaan_id=f.penerimaan_id,
                penerimaan_item_id=f.penerimaan_item_id,
                tipe=f.tipe,
                url=f.url,
            )
            for f in item.fotos
        ],
    )


def _build_response(p: Penerimaan) -> PenerimaanResponse:
    penebusan = p.penebusan
    general_fotos = [f for f in (p.fotos or []) if f.penerimaan_item_id is None]
    total_volume = sum(
        (item.volume_diterima for item in (p.items or [])),
        Decimal("0"),
    )
    return PenerimaanResponse(
        id=p.id,
        spbu_id=p.spbu_id,
        penebusan_id=p.penebusan_id,
        penebusan_booking_code=penebusan.booking_code if penebusan else "",
        penebusan_no_so=penebusan.no_so if penebusan else None,
        tanggal=p.tanggal,
        tgl_jam_keluar_terminal=p.tgl_jam_keluar_terminal,
        jam_tiba=p.jam_tiba,
        jam_berangkat=p.jam_berangkat,
        no_polisi=p.no_polisi,
        shipment_no=p.shipment_no,
        nama_pengemudi=p.nama_pengemudi,
        no_lo=p.no_lo,
        density_obs=p.density_obs,
        temp_obs=p.temp_obs,
        density_ons=p.density_ons,
        temp_ons=p.temp_ons,
        catatan=p.catatan,
        items=[_build_item_response(item) for item in (p.items or [])],
        fotos=[
            PenerimaanFotoResponse(
                id=f.id,
                penerimaan_id=f.penerimaan_id,
                penerimaan_item_id=f.penerimaan_item_id,
                tipe=f.tipe,
                url=f.url,
            )
            for f in general_fotos
        ],
        total_volume_diterima=total_volume,
        created_by_name=p.created_by.name if p.created_by else None,
        created_at=p.created_at,
        status=p.status if isinstance(p.status, str) else p.status.value,
        submitted_by_name=p.submitted_by.name if p.submitted_by else None,
        submitted_at=p.submitted_at,
        reviewed_by_name=p.reviewed_by.name if p.reviewed_by else None,
        reviewed_at=p.reviewed_at,
        catatan_review=p.catatan_review,
        unlocked_by_name=p.unlocked_by.name if p.unlocked_by else None,
        unlocked_at=p.unlocked_at,
        unlock_reason=p.unlock_reason,
    )


# ---------------------------------------------------------------------------
# Public service functions
# ---------------------------------------------------------------------------

async def list_penerimaan(
    db: AsyncSession,
    spbu_id: int,
    tanggal=None,
    penebusan_id: int | None = None,
    skip: int = 0,
    limit: int = 20,
) -> tuple[list[PenerimaanResponse], int]:
    rows, total = await penerimaan_repository.get_all_penerimaan(
        db, spbu_id, tanggal=tanggal, penebusan_id=penebusan_id, skip=skip, limit=limit,
    )
    return [_build_response(r) for r in rows], total


async def get_penerimaan_detail(
    db: AsyncSession, spbu_id: int, penerimaan_id: int
) -> PenerimaanResponse:
    p = await penerimaan_repository.get_penerimaan_by_id(db, penerimaan_id, spbu_id)
    if p is None:
        raise ValueError(f"Penerimaan id={penerimaan_id} tidak ditemukan")
    return _build_response(p)


async def create_penerimaan(
    db: AsyncSession,
    spbu_id: int,
    data: PenerimaanCreate,
    current_user_id: int,
) -> PenerimaanResponse:
    # Validate penebusan belongs to SPBU and has a SO (unless manual)
    penebusan = await penebusan_repository.get_penebusan_by_id(db, data.penebusan_id, spbu_id)
    if penebusan is None:
        raise ValueError(f"Penebusan id={data.penebusan_id} tidak ditemukan")
    if penebusan.status in (StatusPenebusan.DRAFT, StatusPenebusan.WAITING_SO):
        if not penebusan.is_manual:
            raise ValueError(
                "Penerimaan hanya bisa dicatat setelah penebusan memiliki SO "
                "(status: submitted/partially_received/fully_received)"
            )

    # For each item: load tangki with kalibrasi, interpolate volumes
    items_dicts: list[dict] = []
    for item_in in data.items:
        tangki = await _load_tangki(db, item_in.tangki_id, spbu_id)
        volume_sebelum = _interpolate(tangki, item_in.dipstick_sebelum_mm)
        volume_sesudah = _interpolate(tangki, item_in.dipstick_sesudah_mm)
        volume_diterima = volume_sesudah - volume_sebelum
        if volume_diterima <= Decimal("0"):
            raise ValueError(
                f"Tangki '{tangki.nama}': volume sesudah ({volume_sesudah} L) harus lebih besar "
                f"dari volume sebelum ({volume_sebelum} L)"
            )
        items_dicts.append(
            {
                "penebusan_item_id": item_in.penebusan_item_id,
                "produk_id": item_in.produk_id,
                "tangki_id": item_in.tangki_id,
                "dipstick_sebelum_mm": item_in.dipstick_sebelum_mm,
                "volume_sebelum": volume_sebelum,
                "dipstick_sesudah_mm": item_in.dipstick_sesudah_mm,
                "volume_sesudah": volume_sesudah,
                "volume_diterima": volume_diterima,
                "atg_sebelum_mm": item_in.atg_sebelum_mm,
                "atg_sesudah_mm": item_in.atg_sesudah_mm,
            }
        )

    header_dict = {
        "spbu_id": spbu_id,
        "penebusan_id": data.penebusan_id,
        "tanggal": data.tanggal,
        "tgl_jam_keluar_terminal": data.tgl_jam_keluar_terminal,
        "jam_tiba": data.jam_tiba,
        "jam_berangkat": data.jam_berangkat,
        "no_polisi": data.no_polisi,
        "shipment_no": data.shipment_no,
        "nama_pengemudi": data.nama_pengemudi,
        "no_lo": data.no_lo,
        "density_obs": data.density_obs,
        "temp_obs": data.temp_obs,
        "density_ons": data.density_ons,
        "temp_ons": data.temp_ons,
        "catatan": data.catatan,
        "created_by_id": current_user_id,
    }

    try:
        p = await penerimaan_repository.create_penerimaan(db, header_dict, items_dicts)
        await _update_penebusan_after_receipt(db, penebusan, items_dicts)
        await db.commit()
        p = await penerimaan_repository.get_penerimaan_by_id(db, p.id, spbu_id)
        return _build_response(p)
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data penerimaan duplikat atau melanggar constraint database")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def _update_penebusan_after_receipt(
    db: AsyncSession,
    penebusan: Penebusan,
    items_dicts: list[dict],
) -> None:
    """Update penebusan item volume_diterima and auto-transition penebusan status."""
    fresh = await penebusan_repository.get_penebusan_by_id(db, penebusan.id, penebusan.spbu_id)
    if fresh is None:
        return

    # For each received item that links to a penebusan_item, update volume_diterima
    for item_dict in items_dicts:
        pb_item_id = item_dict.get("penebusan_item_id")
        if pb_item_id:
            for pb_item in fresh.items:
                if pb_item.id == pb_item_id:
                    pb_item.volume_diterima = (pb_item.volume_diterima or Decimal("0")) + item_dict["volume_diterima"]
                    break
        else:
            # No linked penebusan_item: try to match by produk
            produk_id = item_dict.get("produk_id")
            if produk_id:
                for pb_item in fresh.items:
                    if pb_item.produk_id == produk_id:
                        pb_item.volume_diterima = (pb_item.volume_diterima or Decimal("0")) + item_dict["volume_diterima"]
                        break

    # Auto-transition status
    total_pesan = sum(i.volume_pesan for i in fresh.items)
    total_diterima = sum(i.volume_diterima for i in fresh.items)

    new_status = None
    if total_diterima >= total_pesan:
        new_status = StatusPenebusan.FULLY_RECEIVED
    elif total_diterima > Decimal("0"):
        new_status = StatusPenebusan.PARTIALLY_RECEIVED

    if new_status and fresh.status not in (StatusPenebusan.FULLY_RECEIVED,):
        fresh.status = new_status
    # Caller (create_penerimaan) owns the commit


async def delete_penerimaan(
    db: AsyncSession, spbu_id: int, penerimaan_id: int
) -> None:
    from app.utils.file_upload import delete_file
    p = await penerimaan_repository.get_penerimaan_by_id(db, penerimaan_id, spbu_id)
    if p is None:
        raise ValueError(f"Penerimaan id={penerimaan_id} tidak ditemukan")
    if p.status != StatusPenerimaan.DRAFT:
        raise ValueError("Penerimaan yang sudah di-submit tidak bisa dihapus")
    foto_urls = [f.url for f in (p.fotos or [])]
    try:
        await penerimaan_repository.delete_penerimaan(db, p)
        await db.commit()
    except IntegrityError:
        await db.rollback()
        raise ValueError("Tidak dapat menghapus penerimaan karena masih ada data terkait")
    except SQLAlchemyError:
        await db.rollback()
        raise
    for url in foto_urls:
        await delete_file(url)


async def add_foto(
    db: AsyncSession,
    spbu_id: int,
    penerimaan_id: int,
    item_id: int | None,
    tipe: str,
    file_bytes: bytes,
    filename: str,
) -> PenerimaanResponse:
    p = await penerimaan_repository.get_penerimaan_by_id(db, penerimaan_id, spbu_id)
    if p is None:
        raise ValueError(f"Penerimaan id={penerimaan_id} tidak ditemukan")
    if p.status != StatusPenerimaan.DRAFT:
        raise ValueError("Penerimaan yang sudah di-submit tidak bisa diedit")

    # Validate tipe enum
    valid_tipes = {e.value for e in TipeFotoEnum}
    if tipe not in valid_tipes:
        raise ValueError(f"Tipe foto tidak valid: {tipe}. Pilihan: {valid_tipes}")

    from app.utils.file_upload import get_spbu_code
    spbu_code = await get_spbu_code(db, spbu_id)
    ctx = UploadContext(spbu_code, "penerimaan", p.tanggal)
    url = await save_upload(file_bytes, filename, ctx)

    try:
        await penerimaan_repository.add_foto(db, penerimaan_id, item_id, tipe, url)
        await db.commit()
    except IntegrityError:
        await db.rollback()
        raise ValueError("Gagal menyimpan foto penerimaan")
    except SQLAlchemyError:
        await db.rollback()
        raise

    p = await penerimaan_repository.get_penerimaan_by_id(db, penerimaan_id, spbu_id)
    return _build_response(p)


async def _user_can_approve_penerimaan(db, user, spbu_id: int) -> bool:
    """Return True if user has penerimaan:approve permission for this SPBU."""
    if user.is_superadmin:
        return True
    assignment = next((a for a in (user.assignments or []) if a.spbu_id == spbu_id), None)
    if assignment is None:
        return False
    from app.models.role import AksiEnum, ModulEnum
    from app.repositories import role_repository
    return await role_repository.has_permission(db, assignment.role_id, ModulEnum.penerimaan, AksiEnum.approve)


async def delete_foto(
    db: AsyncSession,
    spbu_id: int,
    penerimaan_id: int,
    foto_id: int,
) -> PenerimaanResponse:
    p = await penerimaan_repository.get_penerimaan_by_id(db, penerimaan_id, spbu_id)
    if p is None:
        raise ValueError(f"Penerimaan id={penerimaan_id} tidak ditemukan")
    if p.status != StatusPenerimaan.DRAFT:
        raise ValueError("Penerimaan yang sudah di-submit tidak bisa diedit")

    from app.utils.file_upload import delete_file
    try:
        foto_url = await penerimaan_repository.delete_foto(db, foto_id, penerimaan_id)
        await db.commit()
    except IntegrityError:
        await db.rollback()
        raise ValueError("Gagal menghapus foto penerimaan")
    except SQLAlchemyError:
        await db.rollback()
        raise
    await delete_file(foto_url)
    p = await penerimaan_repository.get_penerimaan_by_id(db, penerimaan_id, spbu_id)
    return _build_response(p)


async def submit_penerimaan(
    db: AsyncSession, spbu_id: int, penerimaan_id: int, user_id: int
) -> PenerimaanResponse:
    p = await penerimaan_repository.get_penerimaan_by_id(db, penerimaan_id, spbu_id)
    if p is None:
        raise ValueError(f"Penerimaan id={penerimaan_id} tidak ditemukan")
    if p.status != StatusPenerimaan.DRAFT:
        raise ValueError("Hanya penerimaan DRAFT yang bisa di-submit")
    try:
        p.status = StatusPenerimaan.SUBMITTED
        p.submitted_by_id = user_id
        p.submitted_at = datetime.now(timezone.utc)
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise
    return _build_response(await penerimaan_repository.get_penerimaan_by_id(db, penerimaan_id, spbu_id))


async def review_penerimaan(
    db: AsyncSession, spbu_id: int, penerimaan_id: int, user, action: str, catatan: str | None
) -> PenerimaanResponse:
    p = await penerimaan_repository.get_penerimaan_by_id(db, penerimaan_id, spbu_id)
    if p is None:
        raise ValueError(f"Penerimaan id={penerimaan_id} tidak ditemukan")
    if not await _user_can_approve_penerimaan(db, user, spbu_id):
        raise PermissionError("Tidak ada izin untuk approve penerimaan")
    if p.status != StatusPenerimaan.SUBMITTED:
        raise ValueError("Hanya penerimaan SUBMITTED yang bisa di-review")
    new_status = StatusPenerimaan.APPROVED if action == "approve" else StatusPenerimaan.REJECTED
    try:
        p.status = new_status
        p.reviewed_by_id = user.id
        p.reviewed_at = datetime.now(timezone.utc)
        p.catatan_review = catatan
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise
    return _build_response(await penerimaan_repository.get_penerimaan_by_id(db, penerimaan_id, spbu_id))


async def unlock_penerimaan(
    db: AsyncSession, spbu_id: int, penerimaan_id: int, user, alasan: str
) -> PenerimaanResponse:
    p = await penerimaan_repository.get_penerimaan_by_id(db, penerimaan_id, spbu_id)
    if p is None:
        raise ValueError(f"Penerimaan id={penerimaan_id} tidak ditemukan")
    if not await _user_can_approve_penerimaan(db, user, spbu_id):
        raise PermissionError("Tidak ada izin untuk unlock penerimaan")
    if p.status != StatusPenerimaan.APPROVED:
        raise ValueError("Hanya penerimaan APPROVED yang bisa di-unlock")
    if not alasan or not alasan.strip():
        raise ValueError("Alasan unlock wajib diisi")
    try:
        p.status = StatusPenerimaan.DRAFT
        p.unlocked_by_id = user.id
        p.unlocked_at = datetime.now(timezone.utc)
        p.unlock_reason = alasan
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise
    return _build_response(await penerimaan_repository.get_penerimaan_by_id(db, penerimaan_id, spbu_id))
