package pl.firmatpp.kierowca.tracking

import android.Manifest
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.BatteryManager
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import java.util.Locale
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import pl.firmatpp.kierowca.MainActivity
import pl.firmatpp.kierowca.R
import pl.firmatpp.kierowca.data.model.DriverDto
import pl.firmatpp.kierowca.data.model.DriverRouteDto
import pl.firmatpp.kierowca.data.upload.RoutePointOutbox

class ActiveRouteTrackingService : Service(), LocationListener {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private lateinit var locationManager: LocationManager
    private lateinit var routePointOutbox: RoutePointOutbox

    private var routeId: String? = null
    private var driverId: String? = null
    private var routeLabel: String = "Kurs aktywny"
    private var destinationLat: Double? = null
    private var destinationLon: Double? = null
    private var totalDistanceMeters: Double? = null
    private var progressEnabled: Boolean = true
    private var arrived: Boolean = false
    private var lastRemainingMeters: Float? = null

    override fun onCreate() {
        super.onCreate()
        locationManager = getSystemService(LocationManager::class.java)
        routePointOutbox = RoutePointOutbox(this)
        ensureNotificationChannel()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            ACTION_STOP -> {
                stopSelf()
                return START_NOT_STICKY
            }
            ACTION_MARK_ARRIVED -> {
                val id = intent.getStringExtra(EXTRA_ROUTE_ID)
                if (!id.isNullOrBlank() && (routeId == null || id == routeId)) {
                    routeId = id
                    arrived = true
                    startForegroundCompat(buildNotification())
                }
                return START_STICKY
            }
            else -> {
                routeId = intent?.getStringExtra(EXTRA_ROUTE_ID) ?: routeId
                driverId = intent?.getStringExtra(EXTRA_DRIVER_ID) ?: driverId
                routeLabel = intent?.getStringExtra(EXTRA_ROUTE_LABEL) ?: routeLabel
                destinationLat = intent?.takeIf { it.hasExtra(EXTRA_DESTINATION_LAT) }?.getDoubleExtra(EXTRA_DESTINATION_LAT, 0.0)
                    ?: destinationLat
                destinationLon = intent?.takeIf { it.hasExtra(EXTRA_DESTINATION_LON) }?.getDoubleExtra(EXTRA_DESTINATION_LON, 0.0)
                    ?: destinationLon
                totalDistanceMeters = intent?.takeIf { it.hasExtra(EXTRA_TOTAL_DISTANCE_METERS) }?.getDoubleExtra(EXTRA_TOTAL_DISTANCE_METERS, 0.0)
                    ?.takeIf { it > 0.0 }
                    ?: totalDistanceMeters
                progressEnabled = intent?.getBooleanExtra(EXTRA_PROGRESS_ENABLED, true) ?: progressEnabled
                arrived = false
                startForegroundCompat(buildNotification())
                requestLocationUpdates()
                return START_STICKY
            }
        }
    }

    override fun onLocationChanged(location: Location) {
        val activeRouteId = routeId ?: return
        val activeDriverId = driverId ?: return
        val remaining = remainingMeters(location)
        lastRemainingMeters = remaining

        if (!arrived && remaining != null && remaining <= arrivalRadiusMeters(location)) {
            arrived = true
            startForegroundCompat(buildNotification())
        } else {
            startForegroundCompat(buildNotification())
        }

        val (battery, charging) = batterySnapshot()
        val uploadDelaySeconds = uploadDelaySeconds(location, remaining)
        scope.launch {
            routePointOutbox.enqueue(
                routeId = activeRouteId,
                driverId = activeDriverId,
                location = location,
                battery = battery,
                charging = charging,
                uploadDelaySeconds = uploadDelaySeconds,
            )
        }
    }

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

    override fun onDestroy() {
        runCatching { locationManager.removeUpdates(this) }
        scope.cancel()
        super.onDestroy()
    }

    private fun requestLocationUpdates() {
        if (!hasLocationPermission()) {
            return
        }
        runCatching {
            locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, LOCATION_INTERVAL_MS, 0f, this)
        }
        runCatching {
            locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, LOCATION_INTERVAL_MS, 0f, this)
        }
    }

    private fun buildNotification(): Notification {
        val activeRouteId = routeId
        val target = if (arrived) MainActivity.ROUTE_TARGET_FINISH else MainActivity.ROUTE_TARGET_DETAIL
        val contentIntent = PendingIntent.getActivity(
            this,
            if (arrived) 202 else 201,
            Intent(this, MainActivity::class.java).apply {
                flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
                putExtra(MainActivity.EXTRA_ROUTE_ID, activeRouteId)
                putExtra(MainActivity.EXTRA_ROUTE_TARGET, target)
            },
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
        )

        val title = if (arrived) "Jesteś u celu" else routeLabel
        val text = when {
            arrived -> "Otwórz zakończenie kursu i dodaj wagę oraz zdjęcia."
            !progressEnabled -> "Kurs aktywny"
            lastRemainingMeters != null -> "Do celu: ${formatDistance(lastRemainingMeters!!.toDouble())}"
            else -> "Kurs aktywny"
        }

        val builder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle(title)
            .setContentText(text)
            .setContentIntent(contentIntent)
            .setOngoing(!arrived)
            .setOnlyAlertOnce(true)
            .setCategory(NotificationCompat.CATEGORY_PROGRESS)
            .setPriority(NotificationCompat.PRIORITY_LOW)

        val progress = progressPercent()
        if (!arrived && progressEnabled && progress != null) {
            builder.setProgress(100, progress, false)
        }

        return builder.build()
    }

    private fun startForegroundCompat(notification: Notification) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && hasLocationPermission()) {
            startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
        } else {
            startForeground(NOTIFICATION_ID, notification)
        }
    }

    private fun ensureNotificationChannel() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
        val manager = getSystemService(NotificationManager::class.java)
        val channel = NotificationChannel(
            CHANNEL_ID,
            "Aktywny kurs",
            NotificationManager.IMPORTANCE_LOW,
        ).apply {
            description = "Postęp aktywnego kursu i lokalizacja w tle"
        }
        manager.createNotificationChannel(channel)
    }

    private fun remainingMeters(location: Location): Float? {
        val lat = destinationLat ?: return null
        val lon = destinationLon ?: return null
        val target = Location("destination").apply {
            latitude = lat
            longitude = lon
        }
        return location.distanceTo(target)
    }

    private fun arrivalRadiusMeters(location: Location): Float =
        maxOf(120f, if (location.hasAccuracy()) location.accuracy * 2f else 0f)

    private fun progressPercent(): Int? {
        val total = totalDistanceMeters ?: return null
        val remaining = lastRemainingMeters ?: return null
        if (total <= 0.0) return null

        return ((1.0 - (remaining / total)).coerceIn(0.0, 1.0) * 100).toInt()
    }

    private fun uploadDelaySeconds(location: Location, remaining: Float?): Long {
        val speed = if (location.hasSpeed()) location.speed else 0f
        return when {
            arrived || remaining != null && remaining <= 2_500f -> 5L
            speed < 0.8f -> 45L
            speed > 22f -> 20L
            else -> 20L
        }
    }

    private fun batterySnapshot(): Pair<Int?, Boolean> {
        val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
        val level = batteryIntent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
        val scale = batteryIntent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
        val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
        val percent = if (level >= 0 && scale > 0) ((level * 100f) / scale).toInt().coerceIn(0, 100) else null
        val charging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
            status == BatteryManager.BATTERY_STATUS_FULL

        return percent to charging
    }

    private fun hasLocationPermission(): Boolean =
        ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED

    private fun formatDistance(meters: Double): String =
        if (meters >= 1000.0) {
            String.format(Locale("pl", "PL"), "%.1f km", meters / 1000.0)
        } else {
            "${meters.toInt().coerceAtLeast(0)} m"
        }

    companion object {
        private const val CHANNEL_ID = "active_route_tracking"
        private const val NOTIFICATION_ID = 7401
        private const val LOCATION_INTERVAL_MS = 5_000L

        private const val ACTION_START = "pl.firmatpp.kierowca.tracking.START"
        private const val ACTION_STOP = "pl.firmatpp.kierowca.tracking.STOP"
        private const val ACTION_MARK_ARRIVED = "pl.firmatpp.kierowca.tracking.MARK_ARRIVED"

        private const val EXTRA_ROUTE_ID = "routeId"
        private const val EXTRA_DRIVER_ID = "driverId"
        private const val EXTRA_ROUTE_LABEL = "routeLabel"
        private const val EXTRA_DESTINATION_LAT = "destinationLat"
        private const val EXTRA_DESTINATION_LON = "destinationLon"
        private const val EXTRA_TOTAL_DISTANCE_METERS = "totalDistanceMeters"
        private const val EXTRA_PROGRESS_ENABLED = "progressEnabled"

        fun start(context: Context, route: DriverRouteDto, driver: DriverDto, progressEnabled: Boolean = true) {
            val intent = Intent(context, ActiveRouteTrackingService::class.java).apply {
                action = ACTION_START
                putExtra(EXTRA_ROUTE_ID, route.id)
                putExtra(EXTRA_DRIVER_ID, driver.id)
                putExtra(EXTRA_ROUTE_LABEL, route.contractCode?.let { "Kurs #$it" } ?: route.relationLabel)
                putExtra(EXTRA_PROGRESS_ENABLED, progressEnabled)
                route.destinationNavigation?.latitude?.let { putExtra(EXTRA_DESTINATION_LAT, it) }
                route.destinationNavigation?.longitude?.let { putExtra(EXTRA_DESTINATION_LON, it) }
                route.distanceKm?.takeIf { it > 0.0 }?.let { putExtra(EXTRA_TOTAL_DISTANCE_METERS, it * 1000.0) }
            }
            ContextCompat.startForegroundService(context, intent)
        }

        fun stop(context: Context) {
            context.startService(
                Intent(context, ActiveRouteTrackingService::class.java).apply {
                    action = ACTION_STOP
                },
            )
        }

        fun markArrived(context: Context, routeId: String) {
            val intent = Intent(context, ActiveRouteTrackingService::class.java).apply {
                action = ACTION_MARK_ARRIVED
                putExtra(EXTRA_ROUTE_ID, routeId)
            }
            ContextCompat.startForegroundService(context, intent)
        }
    }
}
