package com.atridad.ascently.utils

import android.content.Context
import com.atridad.ascently.data.format.BackupProblem
import com.atridad.ascently.data.format.ClimbDataBackup
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.time.LocalDateTime
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream

object ZipExportImportUtils {

    private const val DATA_JSON_FILENAME = "data.json"
    private const val IMAGES_DIR_NAME = "images"
    private const val METADATA_FILENAME = "metadata.txt"

    /** Creates a ZIP file containing the JSON data and all referenced images */
    fun createExportZip(
        context: Context,
        exportData: ClimbDataBackup,
        referencedImagePaths: Set<String>,
        directory: File? = null,
    ): File {
        val exportDir =
            directory
                ?: File(
                    context.getExternalFilesDir(
                        android.os.Environment.DIRECTORY_DOCUMENTS,
                    ),
                    "Ascently",
                )
        if (!exportDir.exists()) {
            exportDir.mkdirs()
        }

        val timestamp = LocalDateTime.now().toString().replace(":", "-").replace(".", "-")
        val zipFile = File(exportDir, "ascently_export_$timestamp.zip")

        try {
            ZipOutputStream(FileOutputStream(zipFile)).use { zipOut ->
                // Add metadata file first
                val metadata = createMetadata(exportData, referencedImagePaths)
                val metadataEntry = ZipEntry(METADATA_FILENAME)
                zipOut.putNextEntry(metadataEntry)
                zipOut.write(metadata.toByteArray())
                zipOut.closeEntry()

                // Add JSON data file
                val json =
                    Json {
                        prettyPrint = true
                        ignoreUnknownKeys = true
                    }
                val jsonString = json.encodeToString(exportData)

                val jsonEntry = ZipEntry(DATA_JSON_FILENAME)
                zipOut.putNextEntry(jsonEntry)
                zipOut.write(jsonString.toByteArray())
                zipOut.closeEntry()

                // Add images with validation
                var successfulImages = 0
                referencedImagePaths.forEach { imagePath ->
                    try {
                        val imageFile = ImageUtils.getImageFile(context, imagePath)
                        if (imageFile.exists() && imageFile.length() > 0) {
                            val imageEntry = ZipEntry("$IMAGES_DIR_NAME/${imageFile.name}")
                            zipOut.putNextEntry(imageEntry)

                            FileInputStream(imageFile).use { imageInput ->
                                imageInput.copyTo(zipOut)
                            }
                            zipOut.closeEntry()
                            successfulImages++
                        } else {
                            AppLogger.w("ZipExportImportUtils") {
                                "Image file not found or empty: $imagePath"
                            }
                        }
                    } catch (e: Exception) {
                        AppLogger.e("ZipExportImportUtils", e) {
                            "Failed to add image $imagePath: ${e.message}"
                        }
                    }
                }

                // Log export summary
                AppLogger.i("ZipExportImportUtils") {
                    "Export completed: $successfulImages/${referencedImagePaths.size} images included"
                }
            }

            // Validate the created ZIP file
            if (!zipFile.exists() || zipFile.length() == 0L) {
                throw IOException("Failed to create ZIP file: file is empty or doesn't exist")
            }

            return zipFile
        } catch (e: Exception) {
            // Clean up failed export
            if (zipFile.exists()) {
                zipFile.delete()
            }
            throw IOException("Failed to create export ZIP: ${e.message}")
        }
    }

    /** Creates a ZIP file and writes it to a provided URI */
    fun createExportZipToUri(
        context: Context,
        uri: android.net.Uri,
        exportData: ClimbDataBackup,
        referencedImagePaths: Set<String>,
    ) {
        try {
            context.contentResolver.openOutputStream(uri)?.use { outputStream ->
                ZipOutputStream(outputStream).use { zipOut ->
                    // Add metadata file first
                    val metadata = createMetadata(exportData, referencedImagePaths)
                    val metadataEntry = ZipEntry(METADATA_FILENAME)
                    zipOut.putNextEntry(metadataEntry)
                    zipOut.write(metadata.toByteArray())
                    zipOut.closeEntry()

                    // Add JSON data file
                    val json =
                        Json {
                            prettyPrint = true
                            ignoreUnknownKeys = true
                        }
                    val jsonString = json.encodeToString(exportData)

                    val jsonEntry = ZipEntry(DATA_JSON_FILENAME)
                    zipOut.putNextEntry(jsonEntry)
                    zipOut.write(jsonString.toByteArray())
                    zipOut.closeEntry()

                    // Add images with validation
                    var successfulImages = 0
                    referencedImagePaths.forEach { imagePath ->
                        try {
                            val imageFile = ImageUtils.getImageFile(context, imagePath)
                            if (imageFile.exists() && imageFile.length() > 0) {
                                val imageEntry = ZipEntry("$IMAGES_DIR_NAME/${imageFile.name}")
                                zipOut.putNextEntry(imageEntry)

                                FileInputStream(imageFile).use { imageInput ->
                                    imageInput.copyTo(zipOut)
                                }
                                zipOut.closeEntry()
                                successfulImages++
                            }
                        } catch (e: Exception) {
                            AppLogger.e("ZipExportImportUtils", e) {
                                "Failed to add image $imagePath: ${e.message}"
                            }
                        }
                    }

                    AppLogger.i("ZipExportImportUtils") {
                        "Export to URI completed: $successfulImages/${referencedImagePaths.size} images included"
                    }
                }
            }
                ?: throw IOException("Could not open output stream")
        } catch (e: Exception) {
            throw IOException("Failed to create export ZIP to URI: ${e.message}")
        }
    }

    private fun createMetadata(
        exportData: ClimbDataBackup,
        referencedImagePaths: Set<String>,
    ): String {
        return buildString {
            appendLine("Ascently Export Metadata")
            appendLine("========================")
            appendLine("Export Date: ${exportData.exportedAt}")
            appendLine("Version: ${exportData.version}")
            appendLine("Gyms: ${exportData.gyms.size}")
            appendLine("Problems: ${exportData.problems.size}")
            appendLine("Sessions: ${exportData.sessions.size}")
            appendLine("Attempts: ${exportData.attempts.size}")
            appendLine("Referenced Images: ${referencedImagePaths.size}")
            appendLine("Format: ZIP with embedded JSON data and images")
        }
    }

    /** Data class to hold extraction results */
    data class ImportResult(
        val jsonContent: String,
        val importedImagePaths: Map<String, String>, // original filename -> new relative path
    )

    /** Extracts a ZIP file and returns the JSON content and imported image paths */
    fun extractImportZip(context: Context, zipFile: File): ImportResult {
        var jsonContent = ""
        val importedImagePaths = mutableMapOf<String, String>()
        var foundRequiredFiles = mutableSetOf<String>()

        try {
            ZipInputStream(FileInputStream(zipFile)).use { zipIn ->
                var entry = zipIn.nextEntry

                while (entry != null) {
                    when {
                        entry.name == METADATA_FILENAME -> {
                            // Read metadata for validation
                            val metadataContent = zipIn.readBytes().toString(Charsets.UTF_8)
                            foundRequiredFiles.add("metadata")
                            AppLogger.i("ZipExportImportUtils") {
                                "Found metadata: ${metadataContent.lines().take(3).joinToString()}"
                            }
                        }

                        entry.name == DATA_JSON_FILENAME -> {
                            // Read JSON data
                            jsonContent = zipIn.readBytes().toString(Charsets.UTF_8)
                            foundRequiredFiles.add("data")
                        }

                        entry.name.startsWith("$IMAGES_DIR_NAME/") && !entry.isDirectory -> {
                            // Extract image file
                            val originalFilename = entry.name.substringAfter("$IMAGES_DIR_NAME/")

                            try {
                                // Create temporary file to hold the extracted image
                                val tempFile =
                                    File.createTempFile(
                                        "import_image_",
                                        "_$originalFilename",
                                        context.cacheDir,
                                    )

                                FileOutputStream(tempFile).use { output -> zipIn.copyTo(output) }

                                // Validate the extracted image
                                if (tempFile.exists() && tempFile.length() > 0) {
                                    // Import the image to permanent storage
                                    val newPath = ImageUtils.importImageFile(context, tempFile)
                                    if (newPath != null) {
                                        importedImagePaths[originalFilename] = newPath
                                        AppLogger.d("ZipExportImportUtils") {
                                            "Successfully imported image: $originalFilename -> $newPath"
                                        }
                                    } else {
                                        AppLogger.w("ZipExportImportUtils") {
                                            "Failed to import image: $originalFilename"
                                        }
                                    }
                                } else {
                                    AppLogger.w("ZipExportImportUtils") {
                                        "Extracted image is empty: $originalFilename"
                                    }
                                }

                                // Clean up temp file
                                tempFile.delete()
                            } catch (e: Exception) {
                                AppLogger.e("ZipExportImportUtils", e) {
                                    "Failed to process image $originalFilename: ${e.message}"
                                }
                            }
                        }

                        else -> {
                            AppLogger.d("ZipExportImportUtils") {
                                "Skipping ZIP entry: ${entry.name}"
                            }
                        }
                    }

                    zipIn.closeEntry()
                    entry = zipIn.nextEntry
                }
            }

            // Validate that we found the required files
            if (!foundRequiredFiles.contains("data")) {
                throw IOException("Invalid ZIP file: data.json not found")
            }

            if (jsonContent.isBlank()) {
                throw IOException("Invalid ZIP file: data.json is empty")
            }

            AppLogger.i("ZipExportImportUtils") {
                "Import extraction completed: ${importedImagePaths.size} images processed"
            }

            return ImportResult(jsonContent, importedImagePaths)
        } catch (e: Exception) {
            throw IOException("Failed to extract import ZIP: ${e.message}")
        }
    }

    /**
     * Updates image paths in a problem list after import This function maps the old image paths to
     * the new ones after import
     */
    fun updateProblemImagePaths(
        problems: List<BackupProblem>,
        imagePathMapping: Map<String, String>,
    ): List<BackupProblem> {
        return problems.map { problem ->
            val updatedImagePaths =
                (problem.imagePaths ?: emptyList()).mapNotNull { oldPath ->
                    // Extract filename from the old path
                    val filename = oldPath.substringAfterLast("/")
                    imagePathMapping[filename]
                }
            problem.withUpdatedImagePaths(updatedImagePaths)
        }
    }
}
