package de.duffy.screencast

import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.media.projection.MediaProjectionManager
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.material.button.MaterialButton
import de.duffy.screencast.connect.SystemDisplayRoutes
import de.duffy.screencast.connect.WifiDirectController
import de.duffy.screencast.databinding.ActivityDeviceBinding
import java.net.InetAddress

/**
 * Sucht Fernseher, die auf eine Verbindung warten, und stellt sie her - ohne
 * dass am Fernseher irgendetwas eingetippt werden muss.
 *
 * Zwei Wege stehen offen, die Liste mischt beide:
 *  - Der Übertragungsdienst, den Android selbst mitbringt. Vielen Geräten
 *    fehlt nur die Bedienkachel, nicht die Technik. Klappt das, spiegelt das
 *    System selbst - beste Qualität.
 *  - Wi-Fi Direct: die App baut die Verbindung selbst auf und schickt das Bild
 *    im Miracast-Format hinüber.
 */
class DeviceActivity : AppCompatActivity() {

    private lateinit var binding: ActivityDeviceBinding
    private lateinit var routes: SystemDisplayRoutes
    private lateinit var direct: WifiDirectController

    private var pendingPeer: WifiDirectController.Peer? = null
    private var pendingPeerPort = 7236
    private var sinkAddress: InetAddress? = null

    private val projectionRequest = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        val peer = pendingPeer
        if (result.resultCode != RESULT_OK || result.data == null || peer == null) {
            log("Ohne Freigabe der Bildschirmaufnahme geht es nicht weiter")
            pendingPeer = null
            return@registerForActivityResult
        }
        // Erst den Dienst hochfahren: Er hoert ab sofort auf den Fernseher.
        // Der meldet sich naemlich von sich aus, sobald die Direktverbindung
        // steht - und zwar sofort, nicht erst wenn wir bereit sind.
        val intent = Intent(this, CastService::class.java).apply {
            action = CastService.ACTION_START
            putExtra(CastService.EXTRA_MODE, CastService.MODE_MIRACAST)
            putExtra(CastService.EXTRA_RESULT_CODE, result.resultCode)
            putExtra(CastService.EXTRA_RESULT_DATA, result.data)
            putExtra(CastService.EXTRA_SINK_PORT, peer.rtspPort)
            putExtra(CastService.EXTRA_QUALITY, CastService.Quality.HD.ordinal)
        }
        ContextCompat.startForegroundService(this, intent)
        pendingPeerPort = peer.rtspPort

        log("Baue Direktverbindung zu ${peer.name} auf. Am Fernseher ggf. bestätigen.")
        direct.connect(peer)
    }

    private val permissionRequest = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { granted ->
        if (granted.values.any { it }) startSearch()
        else log("Ohne die Berechtigung findet Android keine Geräte in der Nähe")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityDeviceBinding.inflate(layoutInflater)
        setContentView(binding.root)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)

        routes = SystemDisplayRoutes(this)
        direct = WifiDirectController(this)

        routes.onRoutesChanged = { render() }
        direct.onPeersChanged = { peers -> discoveredPeers = peers; render() }
        direct.onLog = { log(it) }
        direct.onConnected = { address, isOwner -> onDirectConnected(address, isOwner) }

        binding.buttonSearch.setOnClickListener { requestPermissionsAndSearch() }
        binding.buttonSystemSettings.setOnClickListener { openSystemCastSettings() }
        binding.buttonDisconnect.setOnClickListener {
            routes.disconnect()
            direct.disconnect()
            startService(Intent(this, CastService::class.java).setAction(CastService.ACTION_STOP))
            log("Verbindung getrennt")
        }

        CastState.log.observe(this) { lines ->
            binding.textLog.text = lines.joinToString("\n")
            binding.scrollLog.post { binding.scrollLog.fullScroll(View.FOCUS_DOWN) }
        }

        CastState.clearLog()
        requestPermissionsAndSearch()
    }

    override fun onDestroy() {
        super.onDestroy()
        routes.stopScan()
        direct.stop()
    }

    override fun onSupportNavigateUp(): Boolean { finish(); return true }

    // ------------------------------------------------------------------ Suche

    private fun requestPermissionsAndSearch() {
        val needed = ArrayList<String>()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            if (checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES) != PackageManager.PERMISSION_GRANTED)
                needed.add(Manifest.permission.NEARBY_WIFI_DEVICES)
        } else {
            if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)
                needed.add(Manifest.permission.ACCESS_FINE_LOCATION)
        }
        if (needed.isEmpty()) startSearch() else permissionRequest.launch(needed.toTypedArray())
    }

    private fun startSearch() {
        log("Suche nach Fernsehern ...")
        routes.startScan()
        if (direct.start()) direct.discover() else log("Dieses Gerät kann kein Wi-Fi Direct")
        render()
        binding.root.postDelayed({ render() }, 4000)
    }

    // -------------------------------------------------------------- Anzeige

    private fun render() {
        val container = binding.deviceList
        container.removeAllViews()
        var count = 0

        for (route in routes.routes()) {
            count++
            addDevice(
                title = route.name,
                subtitle = buildString {
                    append("Über den Übertragungsdienst von Android")
                    if (route.status.isNotEmpty()) append(" · ${route.status}")
                    if (route.connected) append(" · verbunden")
                },
                enabled = route.enabled
            ) { selectSystemRoute(route) }
        }

        for (peer in discoveredPeers) {
            count++
            addDevice(
                title = peer.name,
                subtitle = buildString {
                    append("Direktverbindung über Wi-Fi Direct")
                    if (peer.isDisplaySink) append(" · meldet sich als Bildschirm")
                    if (peer.sessionAvailable) append(" · wartet auf Verbindung")
                },
                enabled = true
            ) { connectDirect(peer) }
        }

        binding.textEmpty.visibility = if (count == 0) View.VISIBLE else View.GONE
    }

    private var discoveredPeers: List<WifiDirectController.Peer> = emptyList()

    private fun addDevice(title: String, subtitle: String, enabled: Boolean, onClick: () -> Unit) {
        val button = MaterialButton(this, null, com.google.android.material.R.attr.materialButtonOutlinedStyle)
        button.text = "$title\n$subtitle"
        button.isAllCaps = false
        button.isEnabled = enabled
        button.setPadding(40, 30, 40, 30)
        button.setOnClickListener { onClick() }
        val params = android.widget.LinearLayout.LayoutParams(
            android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
            android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
        ).apply { bottomMargin = 24 }
        binding.deviceList.addView(button, params)
    }

    // ---------------------------------------------------------- Verbindungen

    private fun selectSystemRoute(route: SystemDisplayRoutes.Route) {
        log("Verbinde über den Systemdienst mit ${route.name} ...")
        if (routes.select(route)) {
            log("Anfrage gestellt. Wenn der Fernseher annimmt, spiegelt Android ab jetzt selbst.")
            Toast.makeText(this, "Verbindung angefordert", Toast.LENGTH_LONG).show()
        } else {
            log("Der Systemdienst hat die Anfrage abgelehnt")
        }
    }

    private fun connectDirect(peer: WifiDirectController.Peer) {
        pendingPeer = peer
        log("Bitte die Bildschirmaufnahme freigeben - danach wird verbunden.")
        val manager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
        projectionRequest.launch(manager.createScreenCaptureIntent())
    }

    private fun onDirectConnected(address: InetAddress, isGroupOwner: Boolean) {
        if (sinkAddress == address) return   // der Broadcast kommt mehrfach
        sinkAddress = address
        log("Direktverbindung steht (${address.hostAddress}). Warte auf den Fernseher.")
        startService(Intent(this, CastService::class.java).apply {
            action = CastService.ACTION_SINK_READY
            putExtra(CastService.EXTRA_SINK_ADDRESS, address.hostAddress)
            putExtra(CastService.EXTRA_SINK_PORT, pendingPeerPort)
        })
    }

    /** Die versteckte Systemseite fürs Spiegeln - falls das Gerät sie hat. */
    private fun openSystemCastSettings() {
        val candidates = listOf(
            Settings.ACTION_CAST_SETTINGS,
            "android.settings.WIFI_DISPLAY_SETTINGS",
            "com.samsung.settings.WIFI_DISPLAY_SETTINGS"
        )
        for (action in candidates) {
            try {
                startActivity(Intent(action).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
                log("Systemeinstellungen geöffnet ($action)")
                return
            } catch (_: Exception) { /* naechsten Kandidaten probieren */ }
        }
        log("Dieses Gerät hat keine eingebaute Seite fürs Bildschirmspiegeln")
        Toast.makeText(this, "Keine Systemseite fürs Spiegeln vorhanden", Toast.LENGTH_LONG).show()
    }

    private fun log(line: String) = CastState.appendLog(line)
}
