package llc.arma.fleet.monitor.domain.service

import llc.arma.fleet.monitor.data.entity.BeaconStatus
import llc.arma.fleet.monitor.data.entity.MachineEntity
import llc.arma.fleet.monitor.data.entity.MachineSortField
import llc.arma.fleet.monitor.data.entity.MachineStateCode
import llc.arma.fleet.monitor.data.entity.SortDirection
import llc.arma.fleet.monitor.data.repository.BeaconRepository
import llc.arma.fleet.monitor.data.repository.DepartmentRepository
import llc.arma.fleet.monitor.data.repository.MachineRepository
import llc.arma.fleet.monitor.data.repository.MachineTypeRepository
import llc.arma.fleet.monitor.presentation.model.CreateMachinePayload
import llc.arma.fleet.monitor.presentation.model.MachinesPage
import llc.arma.fleet.monitor.presentation.model.UpdateMachinePayload
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import org.springframework.stereotype.Service
import java.time.OffsetDateTime

@Service
class MachineService(
    private val machineRepository: MachineRepository,
    private val machineTypeRepository: MachineTypeRepository,
    private val departmentRepository: DepartmentRepository,
    private val beaconRepository: BeaconRepository,
) {
    fun getMachines(
        page: Int,
        pageSize: Int?,
        query: String?,
        sortDirection: SortDirection,
        sortField: MachineSortField,
    ): MachinesPage {
        val pageable = PageRequest.of(
            page.coerceAtLeast(0),
            (pageSize ?: 20).coerceIn(1, 100),
            Sort.by(
                if (sortDirection == SortDirection.ASC) Sort.Direction.ASC else Sort.Direction.DESC,
                when (sortField) {
                    MachineSortField.ID -> "id"
                    MachineSortField.NAME -> "name"
                    MachineSortField.DATE -> "createdAt"
                },
            ),
        )

        val result = if (query.isNullOrBlank()) {
            machineRepository.findAll(pageable)
        } else {
            machineRepository.findByNameContainingIgnoreCaseOrLicensePlateContainingIgnoreCase(query, query, pageable)
        }

        return MachinesPage(
            totalPages = result.totalPages,
            totalElements = result.totalElements,
            page = result.content,
        )
    }

    fun getMachine(id: Long): MachineEntity? = machineRepository.findById(id).orElse(null)

    fun createMachine(payload: CreateMachinePayload): MachineEntity {
        require(!machineRepository.existsByLicensePlate(payload.licensePlate)) { "Госномер уже используется" }

        val type = machineTypeRepository.findById(payload.typeId)
            .orElseThrow { IllegalArgumentException("Тип машины не найден") }
        val department = departmentRepository.findById(payload.departmentId)
            .orElseThrow { IllegalArgumentException("Подразделение не найдено") }

        return machineRepository.save(
            MachineEntity(
                name = payload.name,
                licensePlate = payload.licensePlate,
                type = type,
                department = department,
                description = payload.description,
                lastKnownState = MachineStateCode.NO_DATA,
            ),
        )
    }

    fun updateMachine(id: Long, payload: UpdateMachinePayload): MachineEntity {
        val machine = machineRepository.findById(id).orElseThrow { IllegalArgumentException("Машина не найдена") }

        payload.name?.let { machine.name = it }
        payload.licensePlate?.let { machine.licensePlate = it }
        payload.typeId?.let {
            machine.type = machineTypeRepository.findById(it).orElseThrow { IllegalArgumentException("Тип машины не найден") }
        }
        payload.departmentId?.let {
            machine.department = departmentRepository.findById(it).orElseThrow { IllegalArgumentException("Подразделение не найдено") }
        }
        if (payload.description != null) machine.description = payload.description

        return machineRepository.save(machine)
    }

    fun deleteMachine(id: Long): Boolean {
        if (!machineRepository.existsById(id)) return false
        machineRepository.deleteById(id)
        return true
    }

    fun bindBeaconToMachine(machineId: Long, beaconId: Long): MachineEntity {
        val machine = machineRepository.findById(machineId).orElseThrow { IllegalArgumentException("Машина не найдена") }
        val beacon = beaconRepository.findById(beaconId).orElseThrow { IllegalArgumentException("Маяк не найден") }

        require(beacon.status == BeaconStatus.FREE || machine.beacon?.id == beacon.id) { "Маяк уже привязан" }

        machine.beacon?.let { previousBeacon ->
            previousBeacon.status = BeaconStatus.FREE
            beaconRepository.save(previousBeacon)
        }

        beacon.status = BeaconStatus.BOUND
        beacon.lastSeenAt = beacon.lastSeenAt ?: OffsetDateTime.now()
        machine.beacon = beacon
        machine.lastDataReceivedAt = beacon.lastSeenAt

        beaconRepository.save(beacon)
        return machineRepository.save(machine)
    }

    fun unbindBeaconFromMachine(machineId: Long): MachineEntity {
        val machine = machineRepository.findById(machineId).orElseThrow { IllegalArgumentException("Машина не найдена") }
        val beacon = machine.beacon ?: return machine

        beacon.status = BeaconStatus.FREE
        machine.beacon = null

        beaconRepository.save(beacon)
        return machineRepository.save(machine)
    }
}
