package com.optix.android.data.repository

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.optix.android.data.local.NodeDao
import com.optix.android.data.local.entities.NodeEntity
import com.optix.android.domain.model.HostResult
import com.optix.android.domain.repository.NodeRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class NodeRepositoryImpl @Inject constructor(
    private val dao: NodeDao,
    private val gson: Gson,
) : NodeRepository {

    override fun observeNodes(): Flow<List<HostResult>> =
        dao.observeAll().map { list -> list.map { it.toDomain() } }

    override suspend fun upsertNodes(nodes: List<HostResult>) {
        if (nodes.isEmpty()) return
        dao.upsertAll(nodes.map { it.toEntity() })
    }

    override suspend fun markMissingNodes(activeNodeIds: Set<String>) {
        if (activeNodeIds.isEmpty()) {
            dao.markAllStale()
        } else {
            dao.markMissing(activeNodeIds.toList())
        }
    }

    override suspend fun clear() = dao.clear()

    private val portListType = object : TypeToken<List<Int>>() {}.type

    private fun HostResult.toEntity() = NodeEntity(
        nodeId = stableNodeId(),
        ip = ip,
        hostname = hostname,
        status = status,
        openPortsJson = gson.toJson(openPorts),
        latencyMs = latencyMs,
        macAddress = macAddress,
        deviceType = deviceType,
        alias = alias,
        transport = transport,
        security = security,
        signalLevelDbm = signalLevelDbm,
        note = note,
        firstSeenAt = firstSeenAt.takeIf { it > 0 } ?: System.currentTimeMillis(),
        lastSeenAt = lastSeenAt.takeIf { it > 0 } ?: System.currentTimeMillis(),
        seenCount = seenCount.coerceAtLeast(1),
    )

    private fun NodeEntity.toDomain() = HostResult(
        nodeId = nodeId,
        ip = ip,
        hostname = hostname,
        status = status,
        openPorts = gson.fromJson(openPortsJson, portListType) ?: emptyList(),
        latencyMs = latencyMs,
        macAddress = macAddress,
        deviceType = deviceType,
        alias = alias,
        transport = transport,
        security = security,
        signalLevelDbm = signalLevelDbm,
        note = note,
        firstSeenAt = firstSeenAt,
        lastSeenAt = lastSeenAt,
        seenCount = seenCount,
    )

    private fun HostResult.stableNodeId(): String = nodeId
        ?: when {
            !macAddress.isNullOrBlank() -> "${transport.lowercase()}:${macAddress.lowercase()}"
            transport.equals("wifi", ignoreCase = true) -> "wifi:${ip.lowercase()}"
            else -> "lan:${ip.lowercase()}"
        }
}
