package com.comtropy.cargoradar

import android.Manifest
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
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.Build
import android.os.IBinder
import android.util.Base64
import java.net.HttpURLConnection
import java.net.URL
import java.time.Instant
import java.util.Locale
import java.util.concurrent.Executors

class LocationUploadService : Service(), LocationListener {
    private val uploadExecutor = Executors.newSingleThreadExecutor()
    private lateinit var locationManager: LocationManager

    private var serverUrl: String = ""
    private var driverId: String = ""
    private var vehicleNo: String = ""
    private var deviceToken: String = ""

    override fun onCreate() {
        super.onCreate()
        locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
        createNotificationChannel()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            ACTION_STOP -> {
                stopTracking()
                return START_NOT_STICKY
            }

            ACTION_START -> {
                serverUrl = intent.getStringExtra(EXTRA_SERVER_URL).orEmpty().trimEnd('/')
                driverId = intent.getStringExtra(EXTRA_DRIVER_ID).orEmpty()
                vehicleNo = intent.getStringExtra(EXTRA_VEHICLE_NO).orEmpty()
                deviceToken = intent.getStringExtra(EXTRA_DEVICE_TOKEN).orEmpty()

                startAsForeground("Waiting for location")
                startLocationUpdates()
                return START_STICKY
            }
        }

        return START_NOT_STICKY
    }

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

    override fun onDestroy() {
        stopTracking()
        uploadExecutor.shutdown()
        super.onDestroy()
    }

    override fun onLocationChanged(location: Location) {
        updateNotification("Uploading location · ${location.provider}")
        uploadLocation(location)
    }

    @SuppressLint("MissingPermission")
    private fun startLocationUpdates() {
        if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            updateNotification("Location permission required")
            return
        }

        val providers = listOf(LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER)
            .filter { provider -> locationManager.isProviderEnabled(provider) }

        if (providers.isEmpty()) {
            updateNotification("No location provider enabled")
            return
        }

        for (provider in providers) {
            locationManager.requestLocationUpdates(
                provider,
                MIN_UPDATE_INTERVAL_MS,
                MIN_UPDATE_DISTANCE_M,
                this
            )

            locationManager.getLastKnownLocation(provider)?.let(::uploadLocation)
        }
    }

    private fun stopTracking() {
        locationManager.removeUpdates(this)
        stopForeground(STOP_FOREGROUND_REMOVE)
        stopSelf()
    }

    private fun uploadLocation(location: Location) {
        if (serverUrl.isBlank() || driverId.isBlank() || deviceToken.isBlank()) {
            updateNotification("Server config required")
            return
        }

        val endpoint = "$serverUrl/api/v1/locations"
        val payload = buildLocationPayload(location)

        uploadExecutor.execute {
            val sent = sendPayload(endpoint, payload)

            if (sent) {
                flushQueuedLocations(endpoint)
                updateNotification("Last upload ok · ${Instant.now()}")
            } else {
                enqueueLocationPayload(payload)
                updateNotification("Upload queued · ${queuedPayloads().size}")
            }
        }
    }

    private fun buildLocationPayload(location: Location): String {
        val speedKmh = if (location.hasSpeed()) location.speed.toDouble() * 3.6 else null
        val heading = if (location.hasBearing()) location.bearing.toDouble() else null
        val accuracy = if (location.hasAccuracy()) location.accuracy.toDouble() else null
        val recordedAt = Instant.ofEpochMilli(location.time).toString()

        return "{" +
            "\"driverId\":${json(driverId)}," +
            "\"vehicleNo\":${json(vehicleNo)}," +
            "\"latitude\":${number(location.latitude)}," +
            "\"longitude\":${number(location.longitude)}," +
            "\"accuracy\":${nullableNumber(accuracy)}," +
            "\"speed\":${nullableNumber(speedKmh)}," +
            "\"heading\":${nullableNumber(heading)}," +
            "\"provider\":${json(location.provider.orEmpty())}," +
            "\"recordedAt\":${json(recordedAt)}" +
            "}"
    }

    private fun sendPayload(endpoint: String, payload: String): Boolean {
        return try {
            val connection = (URL(endpoint).openConnection() as HttpURLConnection).apply {
                requestMethod = "POST"
                connectTimeout = 10000
                readTimeout = 10000
                doOutput = true
                setRequestProperty("Content-Type", "application/json; charset=utf-8")
                setRequestProperty("X-Device-Token", deviceToken)
            }

            connection.outputStream.use { stream ->
                stream.write(payload.toByteArray(Charsets.UTF_8))
            }

            val responseCode = connection.responseCode
            connection.disconnect()
            responseCode in 200..299
        } catch (_: Exception) {
            false
        }
    }

    private fun flushQueuedLocations(endpoint: String) {
        val queued = queuedPayloads()
        if (queued.isEmpty()) return

        val remaining = mutableListOf<String>()

        for (payload in queued) {
            if (!sendPayload(endpoint, payload)) {
                remaining.add(payload)
            }
        }

        saveQueuedPayloads(remaining)
    }

    private fun enqueueLocationPayload(payload: String) {
        val queued = queuedPayloads().toMutableList()
        queued.add(payload)

        while (queued.size > MAX_QUEUED_LOCATIONS) {
            queued.removeAt(0)
        }

        saveQueuedPayloads(queued)
    }

    private fun queuedPayloads(): List<String> {
        val encoded = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE)
            .getString(KEY_QUEUED_PAYLOADS, "")
            .orEmpty()

        if (encoded.isBlank()) return emptyList()

        return encoded
            .lineSequence()
            .filter { it.isNotBlank() }
            .mapNotNull { line ->
                try {
                    String(Base64.decode(line, Base64.NO_WRAP), Charsets.UTF_8)
                } catch (_: Exception) {
                    null
                }
            }
            .toList()
    }

    private fun saveQueuedPayloads(payloads: List<String>) {
        val encoded = payloads.joinToString("\n") { payload ->
            Base64.encodeToString(payload.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)
        }

        getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE)
            .edit()
            .putString(KEY_QUEUED_PAYLOADS, encoded)
            .apply()
    }

    private fun startAsForeground(content: String) {
        val notification = buildNotification(content)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            startForeground(
                NOTIFICATION_ID,
                notification,
                ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION
            )
        } else {
            startForeground(NOTIFICATION_ID, notification)
        }
    }

    private fun updateNotification(content: String) {
        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        manager.notify(NOTIFICATION_ID, buildNotification(content))
    }

    private fun buildNotification(content: String): Notification {
        val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification.Builder(this, CHANNEL_ID)
        } else {
            Notification.Builder(this)
        }

        return builder
            .setSmallIcon(R.drawable.ic_stat_location)
            .setContentTitle(getString(R.string.notification_title))
            .setContentText(content)
            .setOngoing(true)
            .build()
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return

        val channel = NotificationChannel(
            CHANNEL_ID,
            getString(R.string.notification_channel_name),
            NotificationManager.IMPORTANCE_LOW
        )
        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        manager.createNotificationChannel(channel)
    }

    private fun json(value: String): String {
        return "\"${value.replace("\\", "\\\\").replace("\"", "\\\"")}\""
    }

    private fun number(value: Double): String {
        return String.format(Locale.US, "%.7f", value)
    }

    private fun nullableNumber(value: Double?): String {
        return value?.let { String.format(Locale.US, "%.3f", it) } ?: "null"
    }

    companion object {
        const val ACTION_START = "com.comtropy.cargoradar.action.START"
        const val ACTION_STOP = "com.comtropy.cargoradar.action.STOP"
        const val EXTRA_SERVER_URL = "server_url"
        const val EXTRA_DRIVER_ID = "driver_id"
        const val EXTRA_VEHICLE_NO = "vehicle_no"
        const val EXTRA_DEVICE_TOKEN = "device_token"

        private const val CHANNEL_ID = "cargo_radar_location"
        private const val NOTIFICATION_ID = 1201
        private const val MIN_UPDATE_INTERVAL_MS = 30_000L
        private const val MIN_UPDATE_DISTANCE_M = 100f
        private const val MAX_QUEUED_LOCATIONS = 500
        private const val PREFERENCES_NAME = "cargo_radar_location_service"
        private const val KEY_QUEUED_PAYLOADS = "queued_payloads"
    }
}
