package com.flagship.abox.manager.testing.fakes

import com.flagship.abox.manager.domain.DeviceName
import com.flagship.abox.manager.domain.DomainError
import com.flagship.abox.manager.domain.DomainResult
import com.flagship.abox.manager.domain.InfraredCode
import com.flagship.abox.manager.domain.InfraredCodeDownloadResult
import com.flagship.abox.manager.domain.InfraredGateway
import com.flagship.abox.manager.domain.ProviderProfileId
import com.flagship.abox.manager.domain.SocketGateway
import com.flagship.abox.manager.domain.SocketPowerState
import com.flagship.abox.manager.domain.SocketStatus
import com.flagship.abox.manager.domain.TemperatureHumidityGateway
import com.flagship.abox.manager.domain.TemperatureHumidityReading
import java.util.Collections
import java.util.concurrent.ConcurrentHashMap

class FakeSocketGateway : SocketGateway {
    private val states = ConcurrentHashMap<DeviceKey, SocketPowerState>()
    private val errors = ConcurrentHashMap<DeviceKey, DomainError>()

    override suspend fun getStatus(
        profileId: ProviderProfileId,
        deviceName: DeviceName,
    ): DomainResult<SocketStatus> = resultFor(profileId, deviceName)

    override suspend fun setPower(
        profileId: ProviderProfileId,
        deviceName: DeviceName,
        powerState: SocketPowerState,
    ): DomainResult<SocketStatus> {
        val key = DeviceKey(profileId, deviceName)
        errors[key]?.let { return DomainResult.Failure(it) }
        states[key] = powerState
        return DomainResult.Success(SocketStatus(profileId, deviceName, powerState))
    }

    fun setError(profileId: ProviderProfileId, deviceName: DeviceName, error: DomainError?) {
        val key = DeviceKey(profileId, deviceName)
        if (error == null) errors.remove(key) else errors[key] = error
    }

    private fun resultFor(
        profileId: ProviderProfileId,
        deviceName: DeviceName,
    ): DomainResult<SocketStatus> {
        val key = DeviceKey(profileId, deviceName)
        errors[key]?.let { return DomainResult.Failure(it) }
        val state = states.getOrPut(key) { SocketPowerState.OFF }
        return DomainResult.Success(SocketStatus(profileId, deviceName, state))
    }
}

class FakeTemperatureHumidityGateway : TemperatureHumidityGateway {
    private val readings = ConcurrentHashMap<DeviceKey, DomainResult<TemperatureHumidityReading>>()

    override suspend fun getReading(
        profileId: ProviderProfileId,
        deviceName: DeviceName,
    ): DomainResult<TemperatureHumidityReading> = readings.getOrPut(DeviceKey(profileId, deviceName)) {
        DomainResult.Success(
            TemperatureHumidityReading(
                profileId = profileId,
                deviceName = deviceName,
                temperature = "23.5",
                humidity = "45",
            ),
        )
    }

    fun setReading(reading: TemperatureHumidityReading) {
        readings[DeviceKey(reading.profileId, reading.deviceName)] = DomainResult.Success(reading)
    }

    fun setError(profileId: ProviderProfileId, deviceName: DeviceName, error: DomainError) {
        readings[DeviceKey(profileId, deviceName)] = DomainResult.Failure(error)
    }
}

class FakeInfraredGateway : InfraredGateway {
    val sentKeys: MutableList<InfraredCall> = Collections.synchronizedList(mutableListOf())
    val learnedKeys: MutableList<InfraredCall> = Collections.synchronizedList(mutableListOf())
    val downloadCalls: MutableList<InfraredDownloadCall> =
        Collections.synchronizedList(mutableListOf())

    @Volatile
    var sendResult: DomainResult<Unit> = DomainResult.Success(Unit)

    @Volatile
    var learnResult: DomainResult<Unit> = DomainResult.Success(Unit)

    @Volatile
    var downloadError: DomainError? = null

    override suspend fun sendKey(
        profileId: ProviderProfileId,
        deviceName: DeviceName,
        key: String,
    ): DomainResult<Unit> {
        sentKeys += InfraredCall(profileId, deviceName, key)
        return sendResult
    }

    override suspend fun learnKey(
        profileId: ProviderProfileId,
        deviceName: DeviceName,
        key: String,
    ): DomainResult<Unit> {
        learnedKeys += InfraredCall(profileId, deviceName, key)
        return learnResult
    }

    override suspend fun downloadCodes(
        profileId: ProviderProfileId,
        codes: List<InfraredCode>,
    ): DomainResult<List<InfraredCodeDownloadResult>> {
        downloadError?.let { return DomainResult.Failure(it) }
        downloadCalls += InfraredDownloadCall(profileId, codes)
        return DomainResult.Success(codes.map { InfraredCodeDownloadResult(it, successful = true) })
    }
}

data class InfraredCall(
    val profileId: ProviderProfileId,
    val deviceName: DeviceName,
    val key: String,
)

data class InfraredDownloadCall(
    val profileId: ProviderProfileId,
    val codes: List<InfraredCode>,
)

private data class DeviceKey(
    val profileId: ProviderProfileId,
    val deviceName: DeviceName,
)
