package com.atridad.openclimb.utils

import android.content.Context
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"
    
    /**
     * Creates a ZIP file containing the JSON data and all referenced images
     * @param context Android context
     * @param exportData The data to export (should be serializable)
     * @param referencedImagePaths Set of image paths referenced in the data
     * @param directory Optional directory to save to, uses default if null
     * @return The created ZIP file
     */
    fun createExportZip(
        context: Context,
        exportData: com.atridad.openclimb.data.repository.ClimbDataExport,
        referencedImagePaths: Set<String>,
        directory: File? = null
    ): File {
        val exportDir = directory ?: File(context.getExternalFilesDir(android.os.Environment.DIRECTORY_DOCUMENTS), "OpenClimb")
        if (!exportDir.exists()) {
            exportDir.mkdirs()
        }
        
        val timestamp = LocalDateTime.now().toString().replace(":", "-").replace(".", "-")
        val zipFile = File(exportDir, "openclimb_export_$timestamp.zip")
        
        ZipOutputStream(FileOutputStream(zipFile)).use { zipOut ->
            // Add JSON data file
            val json = Json { prettyPrint = true }
            val jsonString = json.encodeToString(com.atridad.openclimb.data.repository.ClimbDataExport.serializer(), exportData)
            
            val jsonEntry = ZipEntry(DATA_JSON_FILENAME)
            zipOut.putNextEntry(jsonEntry)
            zipOut.write(jsonString.toByteArray())
            zipOut.closeEntry()
            
            // Add images
            referencedImagePaths.forEach { imagePath ->
                try {
                    val imageFile = ImageUtils.getImageFile(context, imagePath)
                    if (imageFile.exists()) {
                        val imageEntry = ZipEntry("$IMAGES_DIR_NAME/${imageFile.name}")
                        zipOut.putNextEntry(imageEntry)
                        
                        FileInputStream(imageFile).use { imageInput ->
                            imageInput.copyTo(zipOut)
                        }
                        zipOut.closeEntry()
                    }
                } catch (e: Exception) {
                    // Log error but continue with other images
                    e.printStackTrace()
                }
            }
        }
        
        return zipFile
    }
    
    /**
     * Creates a ZIP file and writes it to a provided URI
     * @param context Android context
     * @param uri The URI to write to
     * @param exportData The data to export
     * @param referencedImagePaths Set of image paths referenced in the data
     */
    fun createExportZipToUri(
        context: Context,
        uri: android.net.Uri,
        exportData: com.atridad.openclimb.data.repository.ClimbDataExport,
        referencedImagePaths: Set<String>
    ) {
        context.contentResolver.openOutputStream(uri)?.use { outputStream ->
            ZipOutputStream(outputStream).use { zipOut ->
                // Add JSON data file
                val json = Json { prettyPrint = true }
                val jsonString = json.encodeToString(com.atridad.openclimb.data.repository.ClimbDataExport.serializer(), exportData)
                
                val jsonEntry = ZipEntry(DATA_JSON_FILENAME)
                zipOut.putNextEntry(jsonEntry)
                zipOut.write(jsonString.toByteArray())
                zipOut.closeEntry()
                
                // Add images
                referencedImagePaths.forEach { imagePath ->
                    try {
                        val imageFile = ImageUtils.getImageFile(context, imagePath)
                        if (imageFile.exists()) {
                            val imageEntry = ZipEntry("$IMAGES_DIR_NAME/${imageFile.name}")
                            zipOut.putNextEntry(imageEntry)
                            
                            FileInputStream(imageFile).use { imageInput ->
                                imageInput.copyTo(zipOut)
                            }
                            zipOut.closeEntry()
                        }
                    } catch (e: Exception) {
                        // Log error but continue with other images
                        e.printStackTrace()
                    }
                }
            }
        } ?: throw IOException("Could not open output stream")
    }
    
    /**
     * 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
     * @param context Android context
     * @param zipFile The ZIP file to extract
     * @return ImportResult containing the JSON and image path mappings
     */
    fun extractImportZip(context: Context, zipFile: File): ImportResult {
        var jsonContent = ""
        val importedImagePaths = mutableMapOf<String, String>()
        
        ZipInputStream(FileInputStream(zipFile)).use { zipIn ->
            var entry = zipIn.nextEntry
            
            while (entry != null) {
                when {
                    entry.name == DATA_JSON_FILENAME -> {
                        // Read JSON data
                        jsonContent = zipIn.readBytes().toString(Charsets.UTF_8)
                    }
                    
                    entry.name.startsWith("$IMAGES_DIR_NAME/") && !entry.isDirectory -> {
                        // Extract image file
                        val originalFilename = entry.name.substringAfter("$IMAGES_DIR_NAME/")
                        
                        // Create temporary file to hold the extracted image
                        val tempFile = File.createTempFile("import_image_", "_$originalFilename", context.cacheDir)
                        
                        FileOutputStream(tempFile).use { output ->
                            zipIn.copyTo(output)
                        }
                        
                        // Import the image to permanent storage
                        val newPath = ImageUtils.importImageFile(context, tempFile)
                        if (newPath != null) {
                            importedImagePaths[originalFilename] = newPath
                        }
                        
                        // Clean up temp file
                        tempFile.delete()
                    }
                }
                
                zipIn.closeEntry()
                entry = zipIn.nextEntry
            }
        }
        
        if (jsonContent.isEmpty()) {
            throw IOException("No data.json file found in the ZIP archive")
        }
        
        return ImportResult(jsonContent, importedImagePaths)
    }
    
    /**
     * Utility function to determine if a file is a ZIP file based on extension
     */
    fun isZipFile(filename: String): Boolean {
        return filename.lowercase().endsWith(".zip")
    }
    
    /**
     * 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<com.atridad.openclimb.data.model.Problem>,
        imagePathMapping: Map<String, String>
    ): List<com.atridad.openclimb.data.model.Problem> {
        return problems.map { problem ->
            val updatedImagePaths = problem.imagePaths.mapNotNull { oldPath ->
                // Extract filename from the old path
                val filename = oldPath.substringAfterLast("/")
                imagePathMapping[filename]
            }
            problem.copy(imagePaths = updatedImagePaths)
        }
    }
}
