/*
 * Copyright (c) 2026 Meshtastic LLC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
@file:Suppress("ReturnCount", "TooGenericExceptionCaught")

package org.meshtastic.core.takserver

import co.touchlab.kermit.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
import okio.ByteString.Companion.toByteString
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.repository.CommandSender
import org.meshtastic.core.repository.MeshConfigHandler
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.core.takserver.TAKPacketConversion.toCoTMessage
import org.meshtastic.core.takserver.TAKPacketConversion.toTAKPacket
import org.meshtastic.core.takserver.fountain.CoTHandler
import org.meshtastic.proto.MemberRole
import org.meshtastic.proto.MeshPacket
import org.meshtastic.proto.PortNum
import org.meshtastic.proto.TAKPacket
import org.meshtastic.proto.Team
import kotlin.concurrent.Volatile

class TAKMeshIntegration(
    private val takServerManager: TAKServerManager,
    private val commandSender: CommandSender,
    private val nodeRepository: NodeRepository,
    private val serviceRepository: ServiceRepository,
    private val meshConfigHandler: MeshConfigHandler,
    private val cotHandler: CoTHandler,
) {
    @Volatile private var isRunning = false
    private val jobs = mutableListOf<Job>()
    private var currentTeam: Team = Team.Unspecifed_Color
    private var currentRole: MemberRole = MemberRole.Unspecifed

    fun start(scope: CoroutineScope) {
        if (isRunning) return
        isRunning = true

        takServerManager.start(scope)

        val newJobs =
            listOf(
                // Forward incoming CoT from TAK clients to mesh
                scope.launch { takServerManager.inboundMessages.collect { cotMessage -> sendCoTToMesh(cotMessage) } },

                // Forward incoming ATAK packets from mesh to TAK clients
                scope.launch {
                    serviceRepository.meshPacketFlow
                        .filter {
                            it.decoded?.portnum == PortNum.ATAK_PLUGIN || it.decoded?.portnum == PortNum.ATAK_FORWARDER
                        }
                        .collect { packet -> handleMeshPacket(packet) }
                },

                // Broadcast node positions to TAK clients.
                // mapLatest cancels any in-flight broadcast loop when a new node-map emission arrives,
                // preventing N×M fan-out from stacking up across rapid consecutive updates.
                scope.launch {
                    nodeRepository.nodeDBbyNum
                        .mapLatest { nodes ->
                            nodes.forEach { (_, node) ->
                                takServerManager.broadcastNode(
                                    node = node,
                                    team = currentTeam.toTakTeamName(),
                                    role = currentRole.toTakRoleName(),
                                )
                            }
                        }
                        .collect {}
                },
                scope.launch {
                    meshConfigHandler.moduleConfig
                        .map { it.tak }
                        .distinctUntilChanged()
                        .collect { takConfig ->
                            currentTeam = takConfig?.team ?: Team.Unspecifed_Color
                            currentRole = takConfig?.role ?: MemberRole.Unspecifed
                        }
                },
            )

        jobs.addAll(newJobs)

        Logger.i { "TAK Mesh Integration started" }
    }

    fun stop() {
        if (!isRunning) return
        isRunning = false
        // Cancel all tracked jobs and clear the list
        val toCancel: List<Job>
        toCancel = jobs.toList()
        jobs.clear()
        toCancel.forEach(Job::cancel)
        takServerManager.stop()
        Logger.i { "TAK Mesh Integration stopped" }
    }

    private suspend fun sendCoTToMesh(cotMessage: CoTMessage) {
        val takPacket = cotMessage.toTAKPacket()
        if (takPacket == null) {
            cotHandler.sendGenericCoT(cotMessage)
            return
        }

        val payload = TAKPacket.ADAPTER.encode(takPacket)

        val dataPacket =
            DataPacket(
                to = DataPacket.ID_BROADCAST,
                bytes = payload.toByteString(),
                dataType = PortNum.ATAK_PLUGIN.value,
            )

        commandSender.sendData(dataPacket)
        Logger.d { "Forwarded CoT to mesh as TAKPacket: ${cotMessage.type}" }
    }

    private suspend fun handleMeshPacket(packet: MeshPacket) {
        val payload = packet.decoded?.payload ?: return

        if (packet.decoded?.portnum == PortNum.ATAK_FORWARDER) {
            cotHandler.handleIncomingForwarderPacket(payload.toByteArray(), packet.from)
            return
        }

        val takPacket =
            try {
                TAKPacket.ADAPTER.decode(payload)
            } catch (e: Exception) {
                Logger.w(e) { "Failed to decode TAKPacket from mesh" }
                return
            }

        val cotMessage = takPacket.toCoTMessage() ?: return

        takServerManager.broadcast(cotMessage)
        Logger.d { "Forwarded ATAK mesh packet to TAK clients: ${cotMessage.type}" }
    }
}
