package com.syncflow.worker

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.hilt.work.HiltWorker
import androidx.work.*
import com.syncflow.MainActivity
import com.syncflow.R
import com.syncflow.data.db.SyncPairDao
import com.syncflow.data.db.entities.toDomain
import com.syncflow.data.providers.ProviderFactory
import com.syncflow.data.repository.AccountRepository
import com.syncflow.domain.sync.SyncEngine
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import timber.log.Timber
import java.util.concurrent.TimeUnit

@HiltWorker
class SyncWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParams: WorkerParameters,
    private val syncPairDao: SyncPairDao,
    private val accountRepository: AccountRepository,
    private val syncEngine: SyncEngine,
    private val providerFactory: ProviderFactory,
) : CoroutineWorker(appContext, workerParams) {

    override suspend fun doWork(): Result {
        val pairId = inputData.getLong(KEY_PAIR_ID, -1L)
        if (pairId == -1L) return Result.failure()

        val pair = syncPairDao.getById(pairId) ?: return Result.failure()
        val account = accountRepository.getAccount(pair.accountId) ?: return Result.failure()

        val silent = inputData.getBoolean(KEY_SILENT, false)

        ensureChannels()
        setForeground(buildForegroundInfo(pair.name, "Syncing…"))

        return try {
            val domainPair = pair.toDomain()
            val provider = providerFactory.create(account)
            val result = syncEngine.sync(domainPair, provider) { up, down, del, bytes ->
                setProgress(workDataOf(
                    KEY_PROGRESS_UPLOADED to up,
                    KEY_PROGRESS_DOWNLOADED to down,
                    KEY_PROGRESS_DELETED to del,
                    KEY_PROGRESS_BYTES to bytes,
                ))
            }

            val lines = buildList {
                if (result.uploaded > 0)   add("↑${result.uploaded}")
                if (result.downloaded > 0) add("↓${result.downloaded}")
                if (result.deleted > 0)    add("🗑${result.deleted}")
                if (result.conflicts > 0)  add("⚠${result.conflicts}")
            }
            val resultSummary = if (lines.isEmpty()) "Up to date" else lines.joinToString(" ")

            if (!silent && result.error != null && pair.notifyOnError) {
                notify(
                    id = pairId.toInt() + RESULT_ID_OFFSET,
                    channelId = CHANNEL_ALERTS,
                    title = "${pair.name} — Sync failed",
                    text = result.error.message ?: "Unknown error",
                    priority = NotificationCompat.PRIORITY_DEFAULT,
                )
            } else if (!silent && pair.notifyOnComplete && result.error == null) {
                val fullLines = buildList {
                    if (result.uploaded > 0)   add("↑ Uploaded: ${result.uploaded} file${if (result.uploaded != 1) "s" else ""}")
                    if (result.downloaded > 0) add("↓ Downloaded: ${result.downloaded} file${if (result.downloaded != 1) "s" else ""}")
                    if (result.deleted > 0)    add("🗑 Deleted: ${result.deleted} file${if (result.deleted != 1) "s" else ""}")
                    if (result.conflicts > 0)  add("⚠ ${result.conflicts} conflict${if (result.conflicts != 1) "s" else ""}")
                }
                val summary = if (fullLines.isEmpty()) "Up to date — nothing to sync" else fullLines.joinToString("\n")
                notify(
                    id = pairId.toInt() + RESULT_ID_OFFSET,
                    channelId = CHANNEL_COMPLETE,
                    title = "${pair.name} — Synced",
                    text = if (fullLines.isEmpty()) summary else fullLines.first(),
                    bigText = summary,
                    priority = NotificationCompat.PRIORITY_LOW,
                )
            }

            Result.success(workDataOf(KEY_RESULT_SUMMARY to resultSummary))
        } catch (e: Exception) {
            Timber.e(e, "SyncWorker failed for pair $pairId")
            if (!silent && pair.notifyOnError) {
                notify(
                    id = pairId.toInt() + RESULT_ID_OFFSET,
                    channelId = CHANNEL_ALERTS,
                    title = "${pair.name} — Sync failed",
                    text = e.message ?: "Unknown error",
                    priority = NotificationCompat.PRIORITY_DEFAULT,
                )
            }
            if (runAttemptCount < 3) Result.retry() else Result.failure()
        }
    }

    private fun ensureChannels() {
        val nm = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if (nm.getNotificationChannel(CHANNEL_PROGRESS) == null)
            nm.createNotificationChannel(NotificationChannel(CHANNEL_PROGRESS, "Sync progress", NotificationManager.IMPORTANCE_LOW).apply {
                description = "Shown while a sync is running"
            })
        if (nm.getNotificationChannel(CHANNEL_COMPLETE) == null)
            nm.createNotificationChannel(NotificationChannel(CHANNEL_COMPLETE, "Sync complete", NotificationManager.IMPORTANCE_LOW).apply {
                description = "Summary after each successful sync"
            })
        if (nm.getNotificationChannel(CHANNEL_ALERTS) == null)
            nm.createNotificationChannel(NotificationChannel(CHANNEL_ALERTS, "Sync errors", NotificationManager.IMPORTANCE_DEFAULT).apply {
                description = "Alerts for sync failures and conflicts"
            })
    }

    private fun buildForegroundInfo(pairName: String, status: String): ForegroundInfo {
        val tapIntent = PendingIntent.getActivity(
            applicationContext, 0,
            Intent(applicationContext, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP },
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
        )
        val notification = NotificationCompat.Builder(applicationContext, CHANNEL_PROGRESS)
            .setContentTitle(pairName)
            .setContentText(status)
            .setSmallIcon(R.drawable.ic_sync)
            .setContentIntent(tapIntent)
            .setOngoing(true)
            .build()
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
            ForegroundInfo(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
        else
            ForegroundInfo(NOTIFICATION_ID, notification)
    }

    private fun notify(id: Int, channelId: String, title: String, text: String, priority: Int, bigText: String? = null) {
        val tapIntent = PendingIntent.getActivity(
            applicationContext, id,
            Intent(applicationContext, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP },
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
        )
        val nm = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val builder = NotificationCompat.Builder(applicationContext, channelId)
            .setContentTitle(title)
            .setContentText(text)
            .setSmallIcon(R.drawable.ic_sync)
            .setPriority(priority)
            .setContentIntent(tapIntent)
            .setAutoCancel(true)
        if (bigText != null) {
            builder.setStyle(NotificationCompat.BigTextStyle().bigText(bigText))
        }
        nm.notify(id, builder.build())
    }

    companion object {
        const val KEY_PAIR_ID = "pair_id"
        const val KEY_SILENT = "silent"
        const val KEY_RESULT_SUMMARY = "result_summary"
        const val KEY_PROGRESS_UPLOADED = "prog_up"
        const val KEY_PROGRESS_DOWNLOADED = "prog_down"
        const val KEY_PROGRESS_DELETED = "prog_del"
        const val KEY_PROGRESS_BYTES = "prog_bytes"
        private const val NOTIFICATION_ID = 1001
        private const val RESULT_ID_OFFSET = 2000
        private const val CHANNEL_PROGRESS = "sync_progress"
        private const val CHANNEL_COMPLETE = "sync_complete"
        private const val CHANNEL_ALERTS   = "sync_alerts"

        fun buildOneTimeRequest(pairId: Long, wifiOnly: Boolean, chargingOnly: Boolean, silent: Boolean = false): OneTimeWorkRequest {
            val constraints = Constraints.Builder()
                .setRequiredNetworkType(if (wifiOnly) NetworkType.UNMETERED else NetworkType.CONNECTED)
                .setRequiresCharging(chargingOnly)
                .build()
            return OneTimeWorkRequestBuilder<SyncWorker>()
                .setInputData(workDataOf(KEY_PAIR_ID to pairId, KEY_SILENT to silent))
                .setConstraints(constraints)
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
                .addTag("sync_$pairId")
                .build()
        }

        fun buildPeriodicRequest(pairId: Long, intervalMinutes: Long, wifiOnly: Boolean, chargingOnly: Boolean): PeriodicWorkRequest {
            val constraints = Constraints.Builder()
                .setRequiredNetworkType(if (wifiOnly) NetworkType.UNMETERED else NetworkType.CONNECTED)
                .setRequiresCharging(chargingOnly)
                .build()
            return PeriodicWorkRequestBuilder<SyncWorker>(intervalMinutes, TimeUnit.MINUTES)
                .setInputData(workDataOf(KEY_PAIR_ID to pairId))
                .setConstraints(constraints)
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
                .addTag("sync_$pairId")
                .build()
        }
    }
}
