package de.hackernet.wireguardtv.vpn

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.VpnService
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.os.ParcelFileDescriptor
import androidx.core.app.NotificationCompat
import com.wireguard.android.backend.Backend
import com.wireguard.android.backend.GoBackend
import com.wireguard.android.backend.Tunnel
import com.wireguard.config.Config
import com.wireguard.config.InetEndpoint
import com.wireguard.config.InetNetwork
import com.wireguard.config.Interface
import com.wireguard.config.Peer
import com.wireguard.crypto.Key
import de.hackernet.wireguardtv.R
import de.hackernet.wireguardtv.data.TunnelRepository
import de.hackernet.wireguardtv.model.TunnelConfig
import de.hackernet.wireguardtv.model.TunnelState
import de.hackernet.wireguardtv.model.TunnelStatus
import de.hackernet.wireguardtv.ui.MainActivity
import de.hackernet.wireguardtv.util.IpGeoService
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import java.net.InetAddress

/**
 * WireGuard VPN Tunnel Service
 * Nutzt die offizielle wireguard-android Library
 */
class WireGuardTunnelService : VpnService() {

    private val binder = LocalBinder()
    private var backend: Backend? = null
    private var currentTunnel: WgTunnel? = null
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    
    private val _status = MutableStateFlow<TunnelStatus?>(null)
    val status: StateFlow<TunnelStatus?> = _status.asStateFlow()

    companion object {
        private const val NOTIFICATION_ID = 1337
        private const val CHANNEL_ID = "wireguard_vpn"
        
        const val ACTION_CONNECT = "de.hackernet.wireguardtv.CONNECT"
        const val ACTION_DISCONNECT = "de.hackernet.wireguardtv.DISCONNECT"
        const val EXTRA_TUNNEL_ID = "tunnel_id"

        @Volatile
        private var INSTANCE: WireGuardTunnelService? = null

        fun getInstance(): WireGuardTunnelService? = INSTANCE
    }

    inner class LocalBinder : Binder() {
        fun getService(): WireGuardTunnelService = this@WireGuardTunnelService
    }

    override fun onBind(intent: Intent?): IBinder = binder

    override fun onCreate() {
        super.onCreate()
        INSTANCE = this
        createNotificationChannel()
        initializeBackend()
    }

    override fun onDestroy() {
        super.onDestroy()
        INSTANCE = null
        runBlocking { disconnect() }
        scope.cancel()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Bei Foreground Service sofort Notification zeigen (wichtig für Boot-Start!)
        if (intent?.action == ACTION_CONNECT) {
            startForeground(NOTIFICATION_ID, createConnectingNotification())
        }

        when (intent?.action) {
            ACTION_CONNECT -> {
                val tunnelId = intent.getStringExtra(EXTRA_TUNNEL_ID)
                if (tunnelId != null) {
                    android.util.Log.i("WireGuardTV", "onStartCommand: Starting connect for tunnel $tunnelId")
                    scope.launch { connect(tunnelId) }
                }
            }
            ACTION_DISCONNECT -> {
                scope.launch { disconnect() }
            }
        }
        return START_STICKY
    }

    private fun createConnectingNotification(): Notification {
        val pendingIntent = PendingIntent.getActivity(
            this,
            0,
            Intent(this, MainActivity::class.java),
            PendingIntent.FLAG_IMMUTABLE
        )

        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("WireGuard VPN")
            .setContentText("Verbindung wird aufgebaut...")
            .setSmallIcon(R.drawable.ic_vpn)
            .setOngoing(true)
            .setContentIntent(pendingIntent)
            .build()
    }

    private fun initializeBackend() {
        backend = GoBackend(this)
    }

    /**
     * Tunnel verbinden
     * Falls bereits ein Tunnel verbunden ist, wird dieser zuerst getrennt.
     */
    suspend fun connect(tunnelId: String): Result<Unit> = withContext(Dispatchers.IO) {
        try {
            val repository = TunnelRepository.getInstance(this@WireGuardTunnelService)

            // Falls bereits ein Tunnel läuft, zuerst trennen
            if (currentTunnel != null) {
                android.util.Log.i("WireGuardTV", "Disconnecting existing tunnel before connecting new one")
                currentTunnel?.let { tunnel ->
                    backend?.setState(tunnel, Tunnel.State.DOWN, null)
                }
                currentTunnel = null
            }

            // Falls laut Repository ein anderer Tunnel aktiv ist, diesen als inaktiv markieren
            val previousActiveTunnelId = repository.getActiveTunnelId()
            if (previousActiveTunnelId != null && previousActiveTunnelId != tunnelId) {
                android.util.Log.i("WireGuardTV", "Clearing previous active tunnel: $previousActiveTunnelId")
                repository.setActiveTunnel(null)
            }

            android.util.Log.i("WireGuardTV", "Connecting tunnel: $tunnelId")
            updateStatus(tunnelId, TunnelState.CONNECTING)

            val tunnelConfig = repository.getTunnel(tunnelId)
                ?: return@withContext Result.failure(Exception("Tunnel not found"))

            android.util.Log.i("WireGuardTV", "Tunnel config loaded: ${tunnelConfig.name}")
            android.util.Log.i("WireGuardTV", "Interface addresses: ${tunnelConfig.interface_.addresses}")
            android.util.Log.i("WireGuardTV", "DNS: ${tunnelConfig.interface_.dns}")
            android.util.Log.i("WireGuardTV", "Peers: ${tunnelConfig.peers.size}")
            tunnelConfig.peers.forEachIndexed { i, peer ->
                android.util.Log.i("WireGuardTV", "Peer $i endpoint: ${peer.endpoint}")
                android.util.Log.i("WireGuardTV", "Peer $i allowedIPs: ${peer.allowedIPs}")
            }

            // WireGuard Config erstellen
            val wgConfig = buildWireGuardConfig(tunnelConfig)
            android.util.Log.i("WireGuardTV", "WireGuard config built successfully")

            // Tunnel erstellen
            val tunnel = WgTunnel(tunnelConfig.name)

            // VPN Permission Check
            val vpnIntent = prepare(this@WireGuardTunnelService)
            if (vpnIntent != null) {
                android.util.Log.e("WireGuardTV", "VPN permission not granted")
                updateStatus(tunnelId, TunnelState.ERROR, "VPN permission required")
                return@withContext Result.failure(Exception("VPN permission not granted"))
            }
            android.util.Log.i("WireGuardTV", "VPN permission OK")

            // Tunnel starten
            android.util.Log.i("WireGuardTV", "Starting tunnel...")
            backend?.setState(tunnel, Tunnel.State.UP, wgConfig)
            currentTunnel = tunnel
            android.util.Log.i("WireGuardTV", "Tunnel started, state: ${backend?.getState(tunnel)}")

            // Als aktiv markieren
            repository.setActiveTunnel(tunnelId)

            // Foreground Service starten
            startForeground(NOTIFICATION_ID, createNotification(tunnelConfig.name, true))

            updateStatus(tunnelId, TunnelState.CONNECTED)
            android.util.Log.i("WireGuardTV", "Tunnel connected successfully")

            // Stats Polling starten
            startStatsPolling(tunnelId)

            Result.success(Unit)
        } catch (e: Exception) {
            android.util.Log.e("WireGuardTV", "Tunnel connection failed: ${e.message}", e)
            updateStatus(tunnelId, TunnelState.ERROR, e.message)
            Result.failure(e)
        }
    }

    /**
     * Tunnel trennen
     */
    suspend fun disconnect(): Result<Unit> = withContext(Dispatchers.IO) {
        try {
            currentTunnel?.let { tunnel ->
                backend?.setState(tunnel, Tunnel.State.DOWN, null)
            }
            currentTunnel = null
            
            val repository = TunnelRepository.getInstance(this@WireGuardTunnelService)
            val activeTunnelId = repository.getActiveTunnelId()
            activeTunnelId?.let { updateStatus(it, TunnelState.DISCONNECTED) }
            
            repository.setActiveTunnel(null)
            
            stopForeground(STOP_FOREGROUND_REMOVE)
            
            Result.success(Unit)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    /**
     * Tunnel Status abrufen
     */
    fun isConnected(): Boolean {
        return currentTunnel?.let { tunnel ->
            backend?.getState(tunnel) == Tunnel.State.UP
        } ?: false
    }

    /**
     * WireGuard Config aus unserer TunnelConfig erstellen
     */
    private fun buildWireGuardConfig(config: TunnelConfig): Config {
        val interfaceBuilder = Interface.Builder()
        
        // Private Key
        interfaceBuilder.parsePrivateKey(config.interface_.privateKey)
        
        // Addresses
        config.interface_.addresses.forEach { addr ->
            interfaceBuilder.addAddress(InetNetwork.parse(addr))
        }
        
        // DNS
        config.interface_.dns.forEach { dns ->
            interfaceBuilder.addDnsServer(InetAddress.getByName(dns))
        }
        
        // MTU
        config.interface_.mtu?.let { interfaceBuilder.setMtu(it) }
        
        // Listen Port
        config.interface_.listenPort?.let { interfaceBuilder.setListenPort(it) }

        val configBuilder = Config.Builder()
        configBuilder.setInterface(interfaceBuilder.build())

        // Peers
        config.peers.forEach { peerConfig ->
            val peerBuilder = Peer.Builder()
            
            peerBuilder.parsePublicKey(peerConfig.publicKey)
            
            peerConfig.preSharedKey?.let { 
                peerBuilder.parsePreSharedKey(it) 
            }
            
            peerConfig.endpoint?.let { endpoint ->
                peerBuilder.parseEndpoint(endpoint)
            }
            
            peerConfig.allowedIPs.forEach { ip ->
                peerBuilder.addAllowedIp(InetNetwork.parse(ip))
            }
            
            peerConfig.persistentKeepalive?.let { 
                peerBuilder.setPersistentKeepalive(it) 
            }
            
            configBuilder.addPeer(peerBuilder.build())
        }

        return configBuilder.build()
    }

    /**
     * Stats Polling
     */
    private fun startStatsPolling(tunnelId: String) {
        scope.launch {
            var lastIpCheck = 0L
            var cachedIpInfo: IpGeoService.IpInfo? = null
            val ipCheckInterval = 60_000L  // IP alle 60 Sekunden prüfen

            while (isActive && currentTunnel != null) {
                try {
                    val stats = backend?.getStatistics(currentTunnel!!)

                    // IP-Info holen (initial und dann alle 60 Sekunden)
                    val now = System.currentTimeMillis()
                    if (now - lastIpCheck > ipCheckInterval) {
                        cachedIpInfo = IpGeoService.getPublicIpInfo()
                        lastIpCheck = now
                        android.util.Log.d("WireGuardTV", "IP Info: ${cachedIpInfo?.ip} ${cachedIpInfo?.flag} ${cachedIpInfo?.countryName}")
                    }

                    stats?.let { s ->
                        _status.value = TunnelStatus(
                            tunnelId = tunnelId,
                            state = TunnelState.CONNECTED,
                            rxBytes = s.totalRx(),
                            txBytes = s.totalTx(),
                            lastHandshake = System.currentTimeMillis(),
                            publicIp = cachedIpInfo?.ip,
                            countryCode = cachedIpInfo?.countryCode,
                            countryName = cachedIpInfo?.countryName,
                            countryFlag = cachedIpInfo?.flag
                        )
                    }
                } catch (e: Exception) {
                    // Ignore stats errors
                }
                delay(5000)  // Alle 5 Sekunden
            }
        }
    }

    private fun updateStatus(tunnelId: String, state: TunnelState, error: String? = null) {
        _status.value = TunnelStatus(
            tunnelId = tunnelId,
            state = state,
            errorMessage = error
        )
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                "WireGuard VPN",
                NotificationManager.IMPORTANCE_LOW
            ).apply {
                description = "VPN Tunnel Status"
                setShowBadge(false)
            }
            
            val manager = getSystemService(NotificationManager::class.java)
            manager.createNotificationChannel(channel)
        }
    }

    private fun createNotification(tunnelName: String, connected: Boolean): Notification {
        val intent = Intent(this, MainActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(
            this, 0, intent,
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )

        val disconnectIntent = Intent(this, WireGuardTunnelService::class.java).apply {
            action = ACTION_DISCONNECT
        }
        val disconnectPendingIntent = PendingIntent.getService(
            this, 1, disconnectIntent,
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )

        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("WireGuard VPN")
            .setContentText(if (connected) "Verbunden: $tunnelName" else "Getrennt")
            .setSmallIcon(R.drawable.ic_vpn)
            .setOngoing(connected)
            .setContentIntent(pendingIntent)
            .addAction(
                R.drawable.ic_disconnect,
                "Trennen",
                disconnectPendingIntent
            )
            .build()
    }

    /**
     * Interne Tunnel-Klasse
     */
    private class WgTunnel(private val tunnelName: String) : Tunnel {
        override fun getName(): String = tunnelName
        override fun onStateChange(newState: Tunnel.State) {}
    }
}
