package com.seven.ikar.kotlin.feature.chat

import android.content.ActivityNotFoundException
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.provider.OpenableColumns
import androidx.core.content.FileProvider
import com.seven.ikar.kotlin.core.model.DownloadedAttachmentFile
import com.seven.ikar.kotlin.core.network.UploadFileSource
import com.seven.ikar.kotlin.feature.archive.AndroidArchiveFileService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.IOException
import java.io.InputStream

data class LocalAttachmentFile(
    val fileName: String,
    val contentType: String,
    val fileSizeBytes: Long,
    val localPath: String
)

data class CacheCleanupResult(
    val deletedFiles: Int,
    val deletedBytes: Long
)

internal fun LocalAttachmentFile.toUploadFileSource(): UploadFileSource =
    UploadFileSource(
        fileName = fileName,
        contentType = contentType,
        contentLength = fileSizeBytes
    ) { File(localPath).inputStream() }

class AndroidAttachmentFileService(
    private val context: Context
) {
    private val maxAttachmentSizeBytes = 25L * 1024L * 1024L
    private val attachmentsDirectory: File by lazy {
        File(context.cacheDir, "attachments").apply { mkdirs() }
    }

    suspend fun importFromUri(uri: Uri): LocalAttachmentFile = withContext(Dispatchers.IO) {
        val resolver = context.contentResolver
        val contentType = resolver.getType(uri) ?: "application/octet-stream"
        val fileName = sanitizeFileName(queryDisplayName(uri) ?: "attachment")
        val declaredSize = queryFileSize(uri)
        if (declaredSize != null && declaredSize > maxAttachmentSizeBytes) {
            throw IOException("File must be 25 MB or smaller.")
        }

        val target = File(attachmentsDirectory, "pending_${System.currentTimeMillis()}_$fileName")
        val copiedBytes = try {
            resolver.openInputStream(uri)?.use { input -> copyToFileWithLimit(input, target) }
                ?: throw IOException("Failed to read selected file.")
        } catch (error: Exception) {
            target.delete()
            throw error
        }

        LocalAttachmentFile(
            fileName = fileName,
            contentType = contentType,
            fileSizeBytes = copiedBytes,
            localPath = target.absolutePath
        )
    }

    fun createAttachmentCacheFile(attachmentId: String, fileName: String): File =
        File(attachmentsDirectory, "${attachmentId}_${sanitizeFileName(fileName)}")

    suspend fun cacheDownloadedAttachment(file: DownloadedAttachmentFile): LocalAttachmentFile =
        withContext(Dispatchers.IO) {
            val target = File(file.localPath)
            target.setLastModified(System.currentTimeMillis())
            LocalAttachmentFile(
                fileName = target.name.substringAfter('_', file.fileName),
                contentType = file.contentType,
                fileSizeBytes = file.fileSizeBytes.takeIf { it >= 0L } ?: target.length(),
                localPath = target.absolutePath
            )
        }

    suspend fun enforceCachePolicy(maxCacheMb: Int, keepMediaDays: Int): CacheCleanupResult =
        withContext(Dispatchers.IO) {
            val files = attachmentsDirectory
                .walkTopDown()
                .filter { it.isFile }
                .filterNot { it.name.startsWith("pending_") }
                .toList()
            val cutoffMillis = System.currentTimeMillis() - keepMediaDays.coerceAtLeast(1) * 24L * 60L * 60L * 1000L
            var deletedFiles = 0
            var deletedBytes = 0L

            val retainedAfterAge = files.filter { file ->
                if (file.lastModified() in 1 until cutoffMillis) {
                    val size = file.length()
                    if (file.delete()) {
                        deletedFiles += 1
                        deletedBytes += size
                    }
                    false
                } else {
                    true
                }
            }.toMutableList()

            val maxBytes = maxCacheMb.coerceAtLeast(1) * 1024L * 1024L
            var totalBytes = retainedAfterAge.sumOf { it.length() }
            if (totalBytes > maxBytes) {
                retainedAfterAge
                    .sortedWith(compareBy<File> { it.lastModified().takeIf { value -> value > 0L } ?: Long.MAX_VALUE }.thenBy { it.name })
                    .forEach { file ->
                        if (totalBytes <= maxBytes) {
                            return@forEach
                        }
                        val size = file.length()
                        if (file.delete()) {
                            totalBytes -= size
                            deletedFiles += 1
                            deletedBytes += size
                        }
                    }
            }

            CacheCleanupResult(deletedFiles = deletedFiles, deletedBytes = deletedBytes)
        }

    fun openCachedAttachment(localPath: String, fileName: String, contentType: String) {
        val file = requireExistingFile(localPath)
        val contentUri = contentUriFor(file)

        if (AndroidArchiveFileService.isSupportedZip(fileName, contentType)) {
            openArchiveInternally(contentUri, fileName, contentType)
            return
        }

        val intent = Intent(Intent.ACTION_VIEW).apply {
            setDataAndType(contentUri, contentType)
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            clipData = android.content.ClipData.newRawUri(fileName, contentUri)
        }

        try {
            context.startActivity(intent)
        } catch (error: ActivityNotFoundException) {
            throw IllegalStateException("No app found to open this attachment.")
        }
    }

    private fun openArchiveInternally(contentUri: Uri, fileName: String, contentType: String) {
        val intent = Intent(Intent.ACTION_VIEW).apply {
            setClassName(context.packageName, "${context.packageName}.MainActivity")
            setDataAndType(contentUri, contentType.ifBlank { "application/zip" })
            putExtra(Intent.EXTRA_TITLE, fileName)
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            clipData = android.content.ClipData.newRawUri(fileName, contentUri)
        }

        try {
            context.startActivity(intent)
        } catch (error: ActivityNotFoundException) {
            throw IllegalStateException("Ikar could not open this ZIP archive.")
        }
    }

    fun shareCachedAttachment(localPath: String, fileName: String, contentType: String) {
        val file = requireExistingFile(localPath)
        val contentUri = contentUriFor(file)

        val chooserIntent = Intent.createChooser(
            Intent(Intent.ACTION_SEND).apply {
                type = contentType
                putExtra(Intent.EXTRA_STREAM, contentUri)
                putExtra(Intent.EXTRA_TITLE, fileName)
                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                clipData = android.content.ClipData.newRawUri(fileName, contentUri)
            },
            "Share"
        ).apply {
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        }

        try {
            context.startActivity(chooserIntent)
        } catch (error: ActivityNotFoundException) {
            throw IllegalStateException("No app found to share this attachment.")
        }
    }

    suspend fun saveCachedImageToGallery(localPath: String, fileName: String, contentType: String): Uri =
        withContext(Dispatchers.IO) {
            val file = requireExistingFile(localPath)
            val resolver = context.contentResolver
            val sanitizedFileName = sanitizeFileName(fileName)
            val values = ContentValues().apply {
                put(MediaStore.Images.Media.DISPLAY_NAME, sanitizedFileName)
                put(MediaStore.Images.Media.MIME_TYPE, contentType)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/Ikar")
                    put(MediaStore.Images.Media.IS_PENDING, 1)
                }
            }

            val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
                ?: throw IOException("Failed to create gallery entry.")

            try {
                resolver.openOutputStream(uri)?.use { output ->
                    file.inputStream().use { input -> input.copyTo(output) }
                } ?: throw IOException("Failed to open gallery output stream.")

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    val publishedValues = ContentValues().apply {
                        put(MediaStore.Images.Media.IS_PENDING, 0)
                    }
                    resolver.update(uri, publishedValues, null, null)
                }

                uri
            } catch (error: Exception) {
                resolver.delete(uri, null, null)
                throw error
            }
        }

    private fun requireExistingFile(localPath: String): File {
        val file = File(localPath)
        if (!file.exists()) {
            throw IOException("Cached file not found.")
        }
        file.setLastModified(System.currentTimeMillis())
        return file
    }

    private fun contentUriFor(file: File): Uri =
        FileProvider.getUriForFile(
            context,
            "${context.packageName}.fileprovider",
            file
        )

    private fun queryDisplayName(uri: Uri): String? {
        val resolver = context.contentResolver
        resolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
            val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
            if (nameIndex >= 0 && cursor.moveToFirst()) {
                return cursor.getString(nameIndex)
            }
        }

        return uri.lastPathSegment
    }

    private fun queryFileSize(uri: Uri): Long? {
        val resolver = context.contentResolver
        resolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null)?.use { cursor ->
            val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
            if (sizeIndex >= 0 && cursor.moveToFirst() && !cursor.isNull(sizeIndex)) {
                return cursor.getLong(sizeIndex).takeIf { it >= 0L }
            }
        }

        return null
    }

    private fun copyToFileWithLimit(input: InputStream, target: File): Long {
        val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
        var totalBytes = 0L

        target.outputStream().use { output ->
            while (true) {
                val read = input.read(buffer)
                if (read < 0) {
                    return totalBytes
                }

                totalBytes += read
                if (totalBytes > maxAttachmentSizeBytes) {
                    throw IOException("File must be 25 MB or smaller.")
                }

                output.write(buffer, 0, read)
            }
        }
    }

    private fun sanitizeFileName(value: String): String =
        value.trim().ifBlank { "attachment" }.replace(Regex("[^A-Za-z0-9._-]"), "_")
}
