"""SPBU service — business logic for SPBU management and all its sub-resources."""

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

from app.models.spbu import Island, KontrakSewa, Nozzle, Shift, Spbu, Tangki, Tenant
from app.models.user import User
from app.repositories import spbu_repository
from app.schemas.spbu import (
    IslandCreate, IslandUpdate,
    KontrakSewaCreate, KontrakSewaUpdate,
    NozzleCreate, NozzleUpdate,
    ShiftCreate, ShiftUpdate,
    SpbuCreate, SpbuUpdate,
    TangkiCreate, TangkiUpdate,
    TenantCreate, TenantUpdate,
)


async def list_spbus(db: AsyncSession, user: User, skip: int = 0, limit: int = 50):
    """Return paginated SPBU list. Superadmin sees all; others see only their assigned stations."""
    if user.is_superadmin:
        return await spbu_repository.get_all(db, skip, limit)
    assigned_ids = {a.spbu_id for a in user.assignments}
    if not assigned_ids:
        return [], 0
    return await spbu_repository.get_all(db, skip, limit, spbu_ids=assigned_ids)


async def create_spbu(db: AsyncSession, data: SpbuCreate, user: User) -> Spbu:
    """Create a new SPBU with initial shifts. Restricted to Super Admin."""
    if not user.is_superadmin:
        raise PermissionError("Hanya Super Admin yang dapat membuat SPBU baru")
    shifts = [s.model_dump() for s in data.shifts]
    spbu_data = data.model_dump(exclude={"shifts"})
    try:
        result = await spbu_repository.create(db, shifts=shifts, **spbu_data)
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data SPBU konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def get_spbu(db: AsyncSession, spbu_id: int, user: User) -> Spbu:
    """Fetch an SPBU, enforcing access control for non-superadmin users."""
    spbu = await spbu_repository.get_by_id(db, spbu_id)
    if not spbu:
        raise ValueError("SPBU tidak ditemukan")
    if not user.is_superadmin:
        assigned_ids = {a.spbu_id for a in user.assignments}
        if spbu_id not in assigned_ids:
            raise PermissionError("Tidak punya akses ke SPBU ini")
    return spbu


async def update_spbu(
    db: AsyncSession, spbu_id: int, data: SpbuUpdate, user: User
) -> Spbu:
    """Update scalar settings on an SPBU."""
    spbu = await get_spbu(db, spbu_id, user)
    update_data = data.model_dump(exclude_none=True)
    try:
        result = await spbu_repository.update(db, spbu, **update_data)
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data SPBU konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def delete_spbu(db: AsyncSession, spbu_id: int, user: User) -> None:
    """Delete an SPBU. Restricted to Super Admin. Mode depends on environment_mode config."""
    if not user.is_superadmin:
        raise PermissionError("Only Super Admin can delete a station")
    spbu = await spbu_repository.get_by_id(db, spbu_id)
    if not spbu:
        raise ValueError("Station not found")
    try:
        await spbu_repository.delete_spbu(db, spbu, hard_delete=False)
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise


# --- Shifts ---

async def list_shifts(db: AsyncSession, spbu_id: int, user: User):
    """Return all shifts for an SPBU, after access control check."""
    await get_spbu(db, spbu_id, user)
    return await spbu_repository.get_shifts(db, spbu_id)


async def create_shift(
    db: AsyncSession, spbu_id: int, data: ShiftCreate, user: User
) -> Shift:
    """Create a new shift under an SPBU."""
    await get_spbu(db, spbu_id, user)
    try:
        result = await spbu_repository.create_shift(db, spbu_id, **data.model_dump())
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data shift konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def update_shift(
    db: AsyncSession, spbu_id: int, shift_id: int, data: ShiftUpdate, user: User
) -> Shift:
    """Update a shift, verifying it belongs to the given SPBU."""
    await get_spbu(db, spbu_id, user)
    shift = await spbu_repository.get_shift_by_id(db, shift_id)
    if not shift or shift.spbu_id != spbu_id:
        raise ValueError("Shift tidak ditemukan")
    update_data = data.model_dump(exclude_none=True)
    try:
        result = await spbu_repository.update_shift(db, shift, **update_data)
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data shift konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def delete_shift(
    db: AsyncSession, spbu_id: int, shift_id: int, user: User
) -> None:
    """Delete a shift, verifying it belongs to the given SPBU."""
    await get_spbu(db, spbu_id, user)
    shift = await spbu_repository.get_shift_by_id(db, shift_id)
    if not shift or shift.spbu_id != spbu_id:
        raise ValueError("Shift tidak ditemukan")
    try:
        await spbu_repository.delete_shift(db, shift, hard_delete=False)
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise


# --- Islands ---

async def list_islands(db: AsyncSession, spbu_id: int, user: User) -> list[Island]:
    """Return all islands for an SPBU, after access control check."""
    await get_spbu(db, spbu_id, user)
    return await spbu_repository.get_islands(db, spbu_id)


async def create_island(db: AsyncSession, spbu_id: int, data: IslandCreate, user: User) -> Island:
    """Create a new island under an SPBU."""
    await get_spbu(db, spbu_id, user)
    try:
        result = await spbu_repository.create_island(db, spbu_id, **data.model_dump())
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data island konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def update_island(db: AsyncSession, spbu_id: int, island_id: int, data: IslandUpdate, user: User) -> Island:
    """Update an island, verifying it belongs to the given SPBU."""
    await get_spbu(db, spbu_id, user)
    island = await spbu_repository.get_island_by_id(db, island_id)
    if not island or island.spbu_id != spbu_id:
        raise ValueError("Island tidak ditemukan")
    try:
        result = await spbu_repository.update_island(db, island, **data.model_dump(exclude_none=True))
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data island konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def delete_island(db: AsyncSession, spbu_id: int, island_id: int, user: User) -> None:
    """Delete an island, verifying it belongs to the given SPBU."""
    await get_spbu(db, spbu_id, user)
    island = await spbu_repository.get_island_by_id(db, island_id)
    if not island or island.spbu_id != spbu_id:
        raise ValueError("Island tidak ditemukan")
    try:
        await spbu_repository.delete_island(db, island, hard_delete=False)
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise


# --- Nozzles ---

async def create_nozzle(db: AsyncSession, spbu_id: int, island_id: int, data: NozzleCreate, user: User) -> Nozzle:
    """Create a new nozzle under an island, verifying the island belongs to the given SPBU."""
    await get_spbu(db, spbu_id, user)
    island = await spbu_repository.get_island_by_id(db, island_id)
    if not island or island.spbu_id != spbu_id:
        raise ValueError("Island tidak ditemukan")
    try:
        result = await spbu_repository.create_nozzle(db, island_id, **data.model_dump())
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data nozzle konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def update_nozzle(db: AsyncSession, spbu_id: int, nozzle_id: int, data: NozzleUpdate, user: User) -> Nozzle:
    """Update a nozzle after verifying SPBU access."""
    await get_spbu(db, spbu_id, user)
    nozzle = await spbu_repository.get_nozzle_by_id(db, nozzle_id)
    if not nozzle:
        raise ValueError("Nozzle tidak ditemukan")
    try:
        result = await spbu_repository.update_nozzle(db, nozzle, **data.model_dump(exclude_none=True))
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data nozzle konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def delete_nozzle(db: AsyncSession, spbu_id: int, nozzle_id: int, user: User) -> None:
    """Delete a nozzle after verifying SPBU access."""
    await get_spbu(db, spbu_id, user)
    nozzle = await spbu_repository.get_nozzle_by_id(db, nozzle_id)
    if not nozzle:
        raise ValueError("Nozzle tidak ditemukan")
    try:
        await spbu_repository.delete_nozzle(db, nozzle, hard_delete=False)
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise


# --- Tangkis ---

async def list_tangkis(db: AsyncSession, spbu_id: int, user: User) -> list[Tangki]:
    """Return all underground tanks for an SPBU, after access control check."""
    await get_spbu(db, spbu_id, user)
    return await spbu_repository.get_tangkis(db, spbu_id)


async def create_tangki(db: AsyncSession, spbu_id: int, data: TangkiCreate, user: User) -> Tangki:
    """Create a new underground tank under an SPBU."""
    await get_spbu(db, spbu_id, user)
    try:
        result = await spbu_repository.create_tangki(db, spbu_id, **data.model_dump())
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data tangki konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def update_tangki(db: AsyncSession, spbu_id: int, tangki_id: int, data: TangkiUpdate, user: User) -> Tangki:
    """Update a tangki, verifying it belongs to the given SPBU."""
    await get_spbu(db, spbu_id, user)
    tangki = await spbu_repository.get_tangki_by_id(db, tangki_id)
    if not tangki or tangki.spbu_id != spbu_id:
        raise ValueError("Tangki tidak ditemukan")
    try:
        result = await spbu_repository.update_tangki(db, tangki, **data.model_dump(exclude_none=True))
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data tangki konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def delete_tangki(db: AsyncSession, spbu_id: int, tangki_id: int, user: User) -> None:
    """Delete a tangki, verifying it belongs to the given SPBU."""
    await get_spbu(db, spbu_id, user)
    tangki = await spbu_repository.get_tangki_by_id(db, tangki_id)
    if not tangki or tangki.spbu_id != spbu_id:
        raise ValueError("Tangki tidak ditemukan")
    try:
        await spbu_repository.delete_tangki(db, tangki, hard_delete=False)
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise


async def update_kalibrasi(
    db: AsyncSession, spbu_id: int, tangki_id: int, rows: list[dict], user: User
) -> list:
    """Replace the calibration table for a tangki (full replace, atomically)."""
    await get_spbu(db, spbu_id, user)
    tangki = await spbu_repository.get_tangki_by_id(db, tangki_id)
    if not tangki or tangki.spbu_id != spbu_id:
        raise ValueError("Tangki tidak ditemukan")
    try:
        result = await spbu_repository.replace_kalibrasi(db, tangki_id, rows)
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data kalibrasi konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


# --- Tenants ---

async def list_tenants(db: AsyncSession, spbu_id: int, user: User) -> list[Tenant]:
    """Return all tenants for an SPBU, after access control check."""
    await get_spbu(db, spbu_id, user)
    return await spbu_repository.get_tenants(db, spbu_id)


async def create_tenant(db: AsyncSession, spbu_id: int, data: TenantCreate, user: User) -> Tenant:
    """Create a new tenant under an SPBU."""
    await get_spbu(db, spbu_id, user)
    try:
        result = await spbu_repository.create_tenant(db, spbu_id, **data.model_dump())
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data tenant konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def update_tenant(db: AsyncSession, spbu_id: int, tenant_id: int, data: TenantUpdate, user: User) -> Tenant:
    """Update a tenant, verifying it belongs to the given SPBU."""
    await get_spbu(db, spbu_id, user)
    tenant = await spbu_repository.get_tenant_by_id(db, tenant_id)
    if not tenant or tenant.spbu_id != spbu_id:
        raise ValueError("Tenant tidak ditemukan")
    try:
        result = await spbu_repository.update_tenant(db, tenant, **data.model_dump(exclude_none=True))
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data tenant konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def delete_tenant(db: AsyncSession, spbu_id: int, tenant_id: int, user: User) -> None:
    """Delete a tenant, verifying it belongs to the given SPBU."""
    await get_spbu(db, spbu_id, user)
    tenant = await spbu_repository.get_tenant_by_id(db, tenant_id)
    if not tenant or tenant.spbu_id != spbu_id:
        raise ValueError("Tenant tidak ditemukan")
    try:
        await spbu_repository.delete_tenant(db, tenant, hard_delete=False)
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise


# --- Kontrak Sewa ---

async def create_kontrak(
    db: AsyncSession, spbu_id: int, tenant_id: int, data: KontrakSewaCreate, user: User
) -> KontrakSewa:
    """Create a new lease contract for a tenant, verifying the tenant belongs to the given SPBU."""
    await get_spbu(db, spbu_id, user)
    tenant = await spbu_repository.get_tenant_by_id(db, tenant_id)
    if not tenant or tenant.spbu_id != spbu_id:
        raise ValueError("Tenant tidak ditemukan")
    try:
        result = await spbu_repository.create_kontrak(db, tenant_id, **data.model_dump())
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data kontrak konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def update_kontrak(
    db: AsyncSession, spbu_id: int, kontrak_id: int, data: KontrakSewaUpdate, user: User
) -> KontrakSewa:
    """Update a lease contract after verifying SPBU access."""
    await get_spbu(db, spbu_id, user)
    kontrak = await spbu_repository.get_kontrak_by_id(db, kontrak_id)
    if not kontrak:
        raise ValueError("Kontrak tidak ditemukan")
    try:
        result = await spbu_repository.update_kontrak(db, kontrak, **data.model_dump(exclude_none=True))
        await db.commit()
        return result
    except IntegrityError:
        await db.rollback()
        raise ValueError("Data kontrak konflik atau sudah ada")
    except SQLAlchemyError:
        await db.rollback()
        raise


async def delete_kontrak(db: AsyncSession, spbu_id: int, kontrak_id: int, user: User) -> None:
    """Delete a lease contract after verifying SPBU access."""
    await get_spbu(db, spbu_id, user)
    kontrak = await spbu_repository.get_kontrak_by_id(db, kontrak_id)
    if not kontrak:
        raise ValueError("Kontrak tidak ditemukan")
    try:
        await spbu_repository.delete_kontrak(db, kontrak, hard_delete=False)
        await db.commit()
    except SQLAlchemyError:
        await db.rollback()
        raise
