package com.aletheia.app.audiobook

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.pm.ServiceInfo
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import com.aletheia.app.AletheiaApplication
import com.aletheia.app.R
import com.aletheia.app.model.AudioBook
import java.io.File
import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class AudioBookGenerationService : Service() {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private val jobs = ConcurrentHashMap<Long, Job>()
    private val userCancellations = ConcurrentHashMap.newKeySet<Long>()
    private val app by lazy { application as AletheiaApplication }
    private val client by lazy { RemoteAudioBookClient() }

    override fun onCreate() {
        super.onCreate()
        createNotificationChannel()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val audioBookId = intent?.getLongExtra(EXTRA_AUDIOBOOK_ID, 0L) ?: 0L
        when (intent?.action) {
            ACTION_CANCEL -> cancelGeneration(audioBookId)
            ACTION_START -> if (audioBookId > 0 && jobs[audioBookId]?.isActive != true) {
                startInForeground(audioBookId, 0, "Подключение к серверу…")
                jobs[audioBookId] = scope.launch {
                    try {
                        generate(audioBookId)
                    } finally {
                        jobs.remove(audioBookId)
                        if (jobs.isEmpty()) stopSelf()
                    }
                }
            }
        }
        return START_REDELIVER_INTENT
    }

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

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

    private suspend fun generate(audioBookId: Long) {
        var record = app.audioBookRepository.getById(audioBookId) ?: return
        val book = app.bookRepository.getBookById(record.sourceBookId)
            ?: return fail(record, "Исходная книга не найдена")
        val source = File(book.filePath)
        val partial = app.audioBookRepository.partialOutputFile(audioBookId)
        val output = app.audioBookRepository.outputFile(audioBookId)
        try {
            var remoteJobId = record.remoteJobId
            if (remoteJobId.isNullOrBlank()) {
                record = saveProgress(
                    record.copy(
                        status = AudioBook.Status.GENERATING,
                        processedCharacters = 0,
                        totalCharacters = 0,
                        currentChapter = "Отправка книги на CMP-сервер…",
                        errorMessage = null
                    )
                )
                val created = client.create(source, book.title, book.author)
                remoteJobId = created.id
                record = saveProgress(record.copy(remoteJobId = remoteJobId))
            }

            while (true) {
                val remote = try {
                    client.status(remoteJobId)
                } catch (error: IOException) {
                    record = saveProgress(
                        record.copy(currentChapter = "Нет связи с сервером, повтор через 15 секунд…")
                    )
                    delay(RETRY_DELAY_MS)
                    continue
                }

                record = saveProgress(
                    record.copy(
                        status = AudioBook.Status.GENERATING,
                        processedCharacters = remote.processedCharacters,
                        totalCharacters = remote.totalCharacters,
                        durationMs = remote.durationMs,
                        chaptersJson = remote.chaptersJson,
                        currentChapter = remote.currentChapter ?: stageLabel(remote.stage),
                        errorMessage = null
                    )
                )
                startInForeground(audioBookId, record.progressPercent, record.currentChapter.orEmpty())

                when (remote.status) {
                    "ready" -> break
                    "failed" -> throw IOException(remote.error ?: "CMP-сервер не смог создать аудиокнигу")
                    "cancelled" -> {
                        saveProgress(record.copy(status = AudioBook.Status.CANCELLED, currentChapter = null))
                        return
                    }
                }
                delay(POLL_DELAY_MS)
            }

            partial.delete()
            record = saveProgress(record.copy(currentChapter = "Скачивание готового M4A…"))
            client.download(remoteJobId, partial)
            output.delete()
            check(partial.renameTo(output)) { "Не удалось сохранить готовый M4A-файл" }
            record = saveProgress(
                record.copy(
                    filePath = output.absolutePath,
                    remoteJobId = null,
                    status = AudioBook.Status.READY,
                    processedCharacters = record.totalCharacters,
                    currentChapter = null
                )
            )
            runCatching { client.cancelOrDelete(remoteJobId) }
                .onFailure { Log.w(TAG, "Remote job cleanup failed for id=$remoteJobId", it) }
            notifyComplete(record)
        } catch (cancelled: CancellationException) {
            if (userCancellations.remove(audioBookId)) {
                record.remoteJobId?.let { runCatching { client.cancelOrDelete(it) } }
                partial.delete()
                if (app.audioBookRepository.getById(audioBookId) != null) {
                    saveProgress(record.copy(status = AudioBook.Status.CANCELLED, currentChapter = null))
                }
            }
            throw cancelled
        } catch (error: Throwable) {
            partial.delete()
            Log.e(TAG, "Audiobook generation failed for id=$audioBookId", error)
            fail(record, error.diagnosticMessage())
        }
    }

    private fun cancelGeneration(audioBookId: Long) {
        if (audioBookId <= 0) return
        userCancellations.add(audioBookId)
        val active = jobs[audioBookId]
        if (active != null) {
            active.cancel()
            return
        }
        scope.launch {
            val record = app.audioBookRepository.getById(audioBookId) ?: return@launch
            record.remoteJobId?.let { runCatching { client.cancelOrDelete(it) } }
            app.audioBookRepository.partialOutputFile(audioBookId).delete()
            if (app.audioBookRepository.getById(audioBookId) != null) {
                saveProgress(record.copy(status = AudioBook.Status.CANCELLED, currentChapter = null))
            }
            userCancellations.remove(audioBookId)
        }
    }

    private fun saveProgress(audioBook: AudioBook): AudioBook =
        app.audioBookRepository.save(audioBook).also(::broadcastProgress)

    private fun stageLabel(stage: String): String = when (stage) {
        "queued" -> "В очереди на CMP-сервере…"
        "extracting" -> "Подготовка текста книги…"
        "loading_model" -> "Загрузка мужского голоса Qwen…"
        "synthesizing" -> "Создание аудиокниги…"
        "encoding" -> "Сборка итогового M4A…"
        "ready" -> "Аудиокнига готова"
        else -> "Обработка на CMP-сервере…"
    }

    private fun Throwable.diagnosticMessage(): String =
        generateSequence(this) { it.cause }.take(4).joinToString(" → ") { cause ->
            cause.message?.takeIf(String::isNotBlank) ?: cause.javaClass.simpleName
        }

    private fun fail(record: AudioBook, message: String) {
        val failed = saveProgress(
            record.copy(status = AudioBook.Status.FAILED, currentChapter = null, errorMessage = message)
        )
        notificationManager.notify(
            notificationId(failed.id),
            notification(failed.id, failed.progressPercent, "Ошибка: $message", ongoing = false)
        )
    }

    private fun broadcastProgress(audioBook: AudioBook) {
        sendBroadcast(
            Intent(ACTION_PROGRESS)
                .setPackage(packageName)
                .putExtra(EXTRA_AUDIOBOOK_ID, audioBook.id)
        )
    }

    private fun startInForeground(id: Long, progress: Int, detail: String) {
        ServiceCompat.startForeground(
            this,
            notificationId(id),
            notification(id, progress, detail, ongoing = true),
            if (Build.VERSION.SDK_INT >= 34) ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE else 0
        )
    }

    private fun notifyComplete(audioBook: AudioBook) {
        notificationManager.notify(
            notificationId(audioBook.id),
            notification(audioBook.id, 100, "Аудиокнига готова", ongoing = false)
        )
    }

    private fun notification(id: Long, progress: Int, detail: String, ongoing: Boolean) =
        NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_reader_v2_voice)
            .setContentTitle("Создание аудиокниги · Qwen, мужской голос")
            .setContentText(detail)
            .setOnlyAlertOnce(true)
            .setOngoing(ongoing)
            .setProgress(100, progress, ongoing && progress <= 0)
            .apply {
                if (ongoing) {
                    addAction(
                        0,
                        "Отменить",
                        PendingIntent.getService(
                            this@AudioBookGenerationService,
                            id.toInt(),
                            Intent(this@AudioBookGenerationService, AudioBookGenerationService::class.java)
                                .setAction(ACTION_CANCEL)
                                .putExtra(EXTRA_AUDIOBOOK_ID, id),
                            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
                        )
                    )
                }
            }
            .build()

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            notificationManager.createNotificationChannel(
                NotificationChannel(CHANNEL_ID, "Создание аудиокниг", NotificationManager.IMPORTANCE_LOW)
            )
        }
    }

    private val notificationManager get() = getSystemService(NotificationManager::class.java)
    private fun notificationId(id: Long) = NOTIFICATION_BASE + (id % 10_000).toInt()

    companion object {
        const val ACTION_PROGRESS = "com.aletheia.app.audiobook.PROGRESS"
        private const val ACTION_START = "com.aletheia.app.audiobook.START"
        private const val ACTION_CANCEL = "com.aletheia.app.audiobook.CANCEL"
        const val EXTRA_AUDIOBOOK_ID = "audio_book_id"
        private const val CHANNEL_ID = "audiobook_generation"
        private const val NOTIFICATION_BASE = 52_000
        private const val TAG = "AudioBookGeneration"
        private const val POLL_DELAY_MS = 3_000L
        private const val RETRY_DELAY_MS = 15_000L

        fun start(context: Context, audioBookId: Long) {
            ContextCompat.startForegroundService(
                context,
                Intent(context, AudioBookGenerationService::class.java)
                    .setAction(ACTION_START)
                    .putExtra(EXTRA_AUDIOBOOK_ID, audioBookId)
            )
        }

        fun cancel(context: Context, audioBookId: Long) {
            context.startService(
                Intent(context, AudioBookGenerationService::class.java)
                    .setAction(ACTION_CANCEL)
                    .putExtra(EXTRA_AUDIOBOOK_ID, audioBookId)
            )
        }
    }
}
