package com.rera.mss.service

import com.rera.mss.model.Inverter
import com.rera.mss.model.MonitoredArea
import com.rera.mss.model.User
import com.rera.mss.payload.InsInvMonitoredAreaDto
import com.rera.mss.payload.InverterDto
import com.rera.mss.payload.InverterListDto
import com.rera.mss.repository.InverterRepository
import com.rera.mss.repository.MonitoredAreaRepository
import com.rera.mss.repository.UserRepository
import com.rera.mss.security.UserPrincipal
import org.springframework.stereotype.Component

@Component
class InverterServiceImpl(
        private val inverterRepository: InverterRepository,
        private val userRepository: UserRepository,
        private val monitoredAreaRepository: MonitoredAreaRepository
) : InverterService {

    override fun addInverter(currentUser: UserPrincipal, dto: InverterDto): InverterServiceOperationResult<InverterDto> {
        val user: User = try {
            userRepository.findUserById(currentUser.getId())?:throw NoSuchElementException()
        } catch (e: Exception) {
            return userNotFoundError(e)
        }

        val newInverter = Inverter(
                manufacturer = dto.manufacturer,
                efficiency = dto.efficiency,
                user = user
        )
        return try {
            val savedInverter = inverterRepository.save(newInverter)
            userRepository.save(user)
            InverterServiceOperationResult.Success(InverterDto(
                    id = savedInverter.id,
                    manufacturer = savedInverter.manufacturer,
                    efficiency = savedInverter.efficiency,
                    monitoredAreas = emptySet()
            ))

        } catch (e: Exception) {
            InverterServiceOperationResult.Error("Could not update user data", e)
        }
    }

    override fun getInverters(currentUser: UserPrincipal): InverterServiceOperationResult<InverterListDto> {
        val user: User = try {
            userRepository.findUserById(currentUser.getId())?:throw NoSuchElementException()
        } catch (e: Exception) {
            return userNotFoundError(e)
        }
        return try {
            val userInverters = inverterRepository.findByUser(user)
            val inverterList = userInverters.map { i ->
                InverterDto(
                        id = i.id,
                        manufacturer = i.manufacturer,
                        efficiency = i.efficiency,
                        monitoredAreas = convertMonitoredAreasToDto(i.inverterMonitoredAreas)
                )
            }
            val availableMonitoredAreas = getAvailableMonitoredAreas(user)
            InverterServiceOperationResult.Success(InverterListDto(inverters = inverterList.toMutableSet(), availableMonitoredAreas = availableMonitoredAreas.toMutableSet()))
        } catch (e: Exception) {
            InverterServiceOperationResult.Error("Could not get inverters for user ${user.id}", e)
        }
    }

    override fun updateInverter(currentUser: UserPrincipal, dto: InverterDto): InverterServiceOperationResult<InverterDto> {
        val inverterToUpdate = try {
            inverterRepository.findInverterById(dto.id)?:throw NoSuchElementException() // TODO mely by se vrátit jen invertory pro daného uživatele
        } catch (e: Exception) {
            return InverterServiceOperationResult.Error("Could not update inverter with id: ${dto.id}, not found.", e)
        }

        return try {
            inverterToUpdate.manufacturer = dto.manufacturer
            inverterToUpdate.efficiency = dto.efficiency

            updateMonitoredAreas(dto, inverterToUpdate)

            val updatedInverter = inverterRepository.save(inverterToUpdate)

            InverterServiceOperationResult.Success(InverterDto(
                    id = updatedInverter.id,
                    manufacturer = updatedInverter.manufacturer,
                    efficiency = updatedInverter.efficiency,
                    monitoredAreas = convertMonitoredAreasToDto(updatedInverter.inverterMonitoredAreas)
            ))
        } catch (e: Exception) {
            InverterServiceOperationResult.Error("Could not update inverter with id: ${dto.id}.", e)
        }
    }

    private fun convertMonitoredAreasToDto(inverterMonitoredAreas: Set<MonitoredArea>): MutableSet<InsInvMonitoredAreaDto> {
        return inverterMonitoredAreas.map { ma: MonitoredArea ->
            InsInvMonitoredAreaDto(ma.id, ma.description)
        }.sortedBy { it.id }.toMutableSet()
    }

    private fun userNotFoundError(e: Exception) =
            InverterServiceOperationResult.Error("Did not find user by id, could not therefore get any user inverters.", e)

    private fun getAvailableMonitoredAreas(user: User): List<InsInvMonitoredAreaDto> {
        return monitoredAreaRepository.findByUser(user).filter { ma: MonitoredArea ->
            ma.inverter == null
        }.map { ma: MonitoredArea -> InsInvMonitoredAreaDto(ma.id, ma.description) }
    }

    private fun updateMonitoredAreas(dto: InverterDto, inverterToUpdate: Inverter) {
        val maFromDto = monitoredAreaRepository.findAllById(dto.monitoredAreas.map { ma -> ma.id })
        removeMontitoredAreasNotContainedInDto(inverterToUpdate, maFromDto)
        maFromDto.forEach { ma -> if (!inverterToUpdate.inverterMonitoredAreas.contains(ma)) inverterToUpdate.addMonitoredArea(ma) }
    }

    private fun removeMontitoredAreasNotContainedInDto(inverterToUpdate: Inverter, maFromDto: List<MonitoredArea>) {
        inverterToUpdate.inverterMonitoredAreas.forEach { ma ->
            if (!maFromDto.contains(ma)) {
                val removedArea = inverterToUpdate.removeMonitoredArea(ma)
                removedArea.inverter = null
                monitoredAreaRepository.save(removedArea)
            }
        }
    }
}