package com.zaneschepke.wireguardautotunnel.util

import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.provider.OpenableColumns
import androidx.annotation.RequiresApi
import androidx.core.content.FileProvider
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.util.extensions.getInputStreamFromUri
import com.zaneschepke.wireguardautotunnel.util.extensions.installApk
import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
import java.io.*
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import org.amnezia.awg.config.Config
import timber.log.Timber

class FileUtils(private val context: Context, private val ioDispatcher: CoroutineDispatcher) {

    suspend fun createWgFiles(tunnels: List<TunnelConf>): List<File> =
        withContext(ioDispatcher) {
            tunnels.mapNotNull { config ->
                if (config.wgQuick.isBlank()) {
                    Timber.w("Skipping tunnel ${config.tunName}: empty wgQuick config")
                    return@mapNotNull null
                }
                val file = File(context.cacheDir, "${config.tunName}-wg.conf")
                file.outputStream().use { it.write(config.wgQuick.toByteArray()) }
                Timber.d("Created WG file: ${file.path}, size: ${file.length()} bytes")
                if (file.length() == 0L) {
                    Timber.w("WG file ${file.path} is empty")
                    null
                } else {
                    file
                }
            }
        }

    suspend fun createAmFiles(tunnels: List<TunnelConf>): List<File> =
        withContext(ioDispatcher) {
            tunnels
                .filter { it.amQuick != TunnelConfig.AM_QUICK_DEFAULT && it.amQuick.isNotBlank() }
                .mapNotNull { config ->
                    val file = File(context.cacheDir, "${config.tunName}-am.conf")
                    file.outputStream().use { it.write(config.amQuick.toByteArray()) }
                    Timber.d("Created AM file: ${file.path}, size: ${file.length()} bytes")
                    if (file.length() == 0L) {
                        Timber.w("AM file ${file.path} is empty")
                        null
                    } else {
                        file
                    }
                }
        }

    suspend fun zipAll(zipFile: File, files: List<File>) =
        withContext(ioDispatcher) {
            ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile))).use { zos ->
                files.forEach { file ->
                    if (!file.exists() || file.length() == 0L) {
                        Timber.w("Skipping file ${file.path}: does not exist or empty")
                        return@forEach
                    }
                    val entryName = file.name // Use file name only, avoid complex path logic
                    val entry = ZipEntry(entryName)
                    zos.putNextEntry(entry)
                    FileInputStream(file).use { fis ->
                        fis.copyTo(zos)
                        Timber.d(
                            "Added ${file.path} to zip as $entryName, size: ${file.length()} bytes"
                        )
                    }
                    zos.closeEntry()
                }
                zos.flush()
                Timber.d("Finished zipping: ${zipFile.path}, size: ${zipFile.length()} bytes")
            }
        }

    suspend fun createNewShareFile(name: String): File =
        withContext(ioDispatcher) {
            val sharePath = File(context.filesDir, "external_files")
            if (sharePath.exists()) sharePath.deleteRecursively()
            sharePath.mkdirs()
            val file = File(sharePath, name)
            if (file.exists()) file.delete()
            file.createNewFile()
            Timber.d("Created share file: ${file.path}")
            file
        }

    suspend fun copyFileToUri(sourceFile: File, destinationUri: Uri): Result<Unit> =
        withContext(ioDispatcher) {
            try {
                if (!sourceFile.exists()) {
                    Timber.e("Source file does not exist: ${sourceFile.path}")
                    return@withContext Result.failure(IOException("Source file does not exist"))
                }
                if (!sourceFile.canRead()) {
                    Timber.e("Source file is not readable: ${sourceFile.path}")
                    return@withContext Result.failure(IOException("Source file is not readable"))
                }
                if (sourceFile.length() == 0L) {
                    Timber.e("Source file is empty: ${sourceFile.path}")
                    return@withContext Result.failure(IOException("Source file is empty"))
                }

                Timber.d("Copying file: ${sourceFile.path}, size: ${sourceFile.length()} bytes")
                var bytesCopied = 0L
                FileInputStream(sourceFile).use { inputStream ->
                    context.contentResolver.openOutputStream(destinationUri)?.use { outputStream ->
                        val buffer = ByteArray(1024 * 1024) // 1MB buffer
                        var bytesRead: Int
                        while (inputStream.read(buffer).also { bytesRead = it } != -1) {
                            outputStream.write(buffer, 0, bytesRead)
                            bytesCopied += bytesRead
                            Timber.d("Copied $bytesCopied bytes so far")
                        }
                        outputStream.flush()
                        Timber.d("Total bytes copied: $bytesCopied")
                        Result.success(Unit)
                    }
                        ?: run {
                            Timber.e("Failed to open OutputStream for Uri: $destinationUri")
                            Result.failure(IOException("Failed to open OutputStream for Uri"))
                        }
                }
            } catch (e: IOException) {
                Timber.e(e, "Error copying file to Uri: ${e.message}")
                Result.failure(e)
            }
        }

    private fun getDisplayNameColumnIndex(cursor: Cursor): Int? {
        val columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
        if (columnIndex == -1) return null
        return columnIndex
    }

    private fun getDisplayNameByCursor(cursor: Cursor): String? {
        val move = cursor.moveToFirst()
        if (!move) return null
        val index = getDisplayNameColumnIndex(cursor)
        if (index == null) return index
        return cursor.getString(index)
    }

    private fun isValidUriContentScheme(uri: Uri): Boolean {
        return uri.scheme == Constants.URI_CONTENT_SCHEME
    }

    private fun getFileName(uri: Uri): String {
        return getFileNameByCursor(uri) ?: NumberUtils.generateRandomTunnelName()
    }

    private fun getNameFromFileName(fileName: String): String {
        return fileName.substring(0, fileName.lastIndexOf('.'))
    }

    private fun getFileExtensionFromFileName(fileName: String): String? {
        return try {
            fileName.substring(fileName.lastIndexOf('.'))
        } catch (e: Exception) {
            Timber.e(e)
            null
        }
    }

    private fun getFileNameByCursor(uri: Uri): String? {
        return context.contentResolver.query(uri, null, null, null, null)?.use {
            getDisplayNameByCursor(it)
        }
    }

    suspend fun buildTunnelsFromUri(uri: Uri): List<TunnelConf> =
        withContext(ioDispatcher) {
            if (!isValidUriContentScheme(uri)) throw InvalidFileExtensionException
            val fileName = getFileName(uri)
            when (getFileExtensionFromFileName(fileName)) {
                Constants.CONF_FILE_EXTENSION -> {
                    context.getInputStreamFromUri(uri)?.use { inputStream ->
                        val name = getNameFromFileName(fileName)
                        val amConf = Config.parse(inputStream)
                        listOf(
                            TunnelConf(
                                tunName = name,
                                wgQuick = amConf.toWgQuickString(),
                                amQuick = amConf.toAwgQuickString(true),
                            )
                        )
                    } ?: throw FileReadException
                }
                Constants.ZIP_FILE_EXTENSION -> {
                    ZipInputStream(context.getInputStreamFromUri(uri)).use { zip ->
                        generateSequence { zip.nextEntry }
                            .filterNot {
                                it.isDirectory ||
                                    getFileExtensionFromFileName(it.name) !=
                                        Constants.CONF_FILE_EXTENSION
                            }
                            .map { entry ->
                                val name = getNameFromFileName(entry.name)
                                val amConf = Config.parse(zip.bufferedReader())
                                TunnelConf(
                                    tunName = name,
                                    wgQuick = amConf.toWgQuickString(),
                                    amQuick = amConf.toAwgQuickString(true),
                                )
                            }
                            .toList()
                    }
                }
                else -> throw InvalidFileExtensionException
            }
        }

    fun shareFile(shareFile: File) {
        val uri =
            FileProvider.getUriForFile(context, context.getString(R.string.provider), shareFile)
        context.launchShareFile(uri)
    }

    @RequiresApi(Build.VERSION_CODES.Q)
    suspend fun saveToDownloadsWithMediaStore(file: File, mimeType: String): Uri? =
        withContext(ioDispatcher) {
            val contentValues =
                ContentValues().apply {
                    put(MediaStore.Downloads.DISPLAY_NAME, file.name)
                    put(MediaStore.Downloads.MIME_TYPE, mimeType)
                    put(MediaStore.Downloads.IS_PENDING, 1)
                }

            val resolver = context.contentResolver
            val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
            val itemUri = resolver.insert(collection, contentValues) ?: return@withContext null

            resolver.openOutputStream(itemUri)?.use { output ->
                file.inputStream().use { input -> input.copyTo(output) }
            }
            // Mark as finished
            contentValues.clear()
            contentValues.put(MediaStore.Downloads.IS_PENDING, 0)
            resolver.update(itemUri, contentValues, null, null)

            return@withContext itemUri
        }

    fun installApk(file: File) {
        context.installApk(file)
    }
}
