package com.syncflow.worker

import android.app.*
import android.content.Context
import android.content.Intent
import android.database.ContentObserver
import android.net.Uri
import android.os.Build
import android.os.FileObserver
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import androidx.core.app.NotificationCompat
import androidx.work.ExistingWorkPolicy
import androidx.work.WorkManager
import com.syncflow.MainActivity
import com.syncflow.R
import com.syncflow.data.db.SyncPairDao
import com.syncflow.domain.model.ScheduleType
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.*
import timber.log.Timber
import java.io.File
import javax.inject.Inject

@AndroidEntryPoint
class FileWatchService : Service() {

    @Inject lateinit var syncPairDao: SyncPairDao

    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    private val mainHandler = Handler(Looper.getMainLooper())

    private val fileObservers = mutableMapOf<Long, FileObserver>()
    private val contentObservers = mutableMapOf<Long, ContentObserver>()
    private val debounceJobs = mutableMapOf<Long, Job>()

    companion object {
        const val CHANNEL_WATCH = "sync_watching"
        private const val NOTIFICATION_ID = 1002

        fun start(context: Context) {
            val intent = Intent(context, FileWatchService::class.java)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                context.startForegroundService(intent)
            } else {
                context.startService(intent)
            }
        }

        fun stop(context: Context) {
            context.stopService(Intent(context, FileWatchService::class.java))
        }
    }

    override fun onCreate() {
        super.onCreate()
        ensureChannel()
        startForeground(NOTIFICATION_ID, buildNotification(0))
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        scope.launch { refresh() }
        return START_STICKY
    }

    override fun onDestroy() {
        clearWatchers()
        scope.cancel()
        super.onDestroy()
    }

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

    private suspend fun refresh() {
        clearWatchers()
        val pairs = syncPairDao.getEnabled().filter { it.scheduleType == ScheduleType.ON_CHANGE }

        pairs.forEach { pair ->
            val pairId = pair.id
            val localPath = pair.localPath

            if (localPath.startsWith("content://")) {
                val treeUri = Uri.parse(localPath)
                val observer = object : ContentObserver(mainHandler) {
                    override fun onChange(selfChange: Boolean) = onChangeDetected(pairId, pair.wifiOnly, pair.chargingOnly)
                    override fun onChange(selfChange: Boolean, uri: Uri?) = onChangeDetected(pairId, pair.wifiOnly, pair.chargingOnly)
                }
                contentResolver.registerContentObserver(treeUri, true, observer)
                contentObservers[pairId] = observer
                Timber.d("FileWatchService: watching SAF URI for pair $pairId")
            } else {
                val dir = File(localPath)
                if (!dir.exists()) {
                    Timber.w("FileWatchService: path does not exist for pair $pairId: $localPath")
                    return@forEach
                }
                val mask = FileObserver.CREATE or FileObserver.DELETE or FileObserver.MODIFY or
                           FileObserver.MOVED_FROM or FileObserver.MOVED_TO
                val observer = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    object : FileObserver(dir, mask) {
                        override fun onEvent(event: Int, path: String?) = onChangeDetected(pairId, pair.wifiOnly, pair.chargingOnly)
                    }
                } else {
                    @Suppress("DEPRECATION")
                    object : FileObserver(localPath, mask) {
                        override fun onEvent(event: Int, path: String?) = onChangeDetected(pairId, pair.wifiOnly, pair.chargingOnly)
                    }
                }
                observer.startWatching()
                fileObservers[pairId] = observer
                Timber.d("FileWatchService: watching filesystem path for pair $pairId: $localPath")
            }
        }

        val count = fileObservers.size + contentObservers.size
        updateNotification(count)

        if (count == 0) {
            Timber.d("FileWatchService: no ON_CHANGE pairs, stopping")
            stopSelf()
        }
    }

    private fun onChangeDetected(pairId: Long, wifiOnly: Boolean, chargingOnly: Boolean) {
        debounceJobs[pairId]?.cancel()
        debounceJobs[pairId] = scope.launch {
            delay(5_000)
            val pair = syncPairDao.getById(pairId)
            if (pair == null || !pair.isEnabled) return@launch
            Timber.d("FileWatchService: triggering sync for pair $pairId after debounce")
            val req = SyncWorker.buildOneTimeRequest(pairId, wifiOnly, chargingOnly)
            WorkManager.getInstance(applicationContext)
                .enqueueUniqueWork("onchange_$pairId", ExistingWorkPolicy.KEEP, req)
        }
    }

    private fun clearWatchers() {
        fileObservers.values.forEach { it.stopWatching() }
        fileObservers.clear()
        contentObservers.values.forEach { contentResolver.unregisterContentObserver(it) }
        contentObservers.clear()
        debounceJobs.values.forEach { it.cancel() }
        debounceJobs.clear()
    }

    private fun ensureChannel() {
        val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if (nm.getNotificationChannel(CHANNEL_WATCH) == null) {
            nm.createNotificationChannel(
                NotificationChannel(CHANNEL_WATCH, "File watching", NotificationManager.IMPORTANCE_LOW).apply {
                    description = "Background service watching folders for changes"
                    setShowBadge(false)
                }
            )
        }
    }

    private fun buildNotification(count: Int): Notification {
        val tapIntent = PendingIntent.getActivity(
            this, 0,
            Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP },
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
        )
        return NotificationCompat.Builder(this, CHANNEL_WATCH)
            .setContentTitle("SyncFlow")
            .setContentText(
                if (count > 0) "Watching $count folder${if (count != 1) "s" else ""} for changes"
                else "Starting file watcher…"
            )
            .setSmallIcon(R.drawable.ic_sync)
            .setContentIntent(tapIntent)
            .setOngoing(true)
            .setPriority(NotificationCompat.PRIORITY_MIN)
            .build()
    }

    private fun updateNotification(count: Int) {
        val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        nm.notify(NOTIFICATION_ID, buildNotification(count))
    }
}
