package cz.coolcode.live_vacation_memories

import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import androidx.exifinterface.media.ExifInterface
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import java.io.InputStream
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone

class MainActivity : FlutterActivity() {
    private val CHANNEL = "cz.coolcode.live_vacation_memories/media"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            when (call.method) {
                "scanPhotos" -> {
                    val startUtc = call.argument<Long>("startUtc")
                    val finishUtc = call.argument<Long>("finishUtc")
                    if (startUtc != null && finishUtc != null) {
                        Thread {
                            try {
                                val photos = scanPhotosForTrip(this, startUtc, finishUtc)
                                runOnUiThread { result.success(photos) }
                            } catch (e: Exception) {
                                runOnUiThread { result.error("SCAN_FAILED", e.message, null) }
                            }
                        }.start()
                    } else {
                        result.error("INVALID_ARGUMENTS", "startUtc and finishUtc must not be null", null)
                    }
                }
                "calculateSha256" -> {
                    val uriString = call.argument<String>("uri")
                    if (uriString != null) {
                        Thread {
                            try {
                                val hash = calculateSha256ForUri(this, uriString)
                                runOnUiThread { result.success(hash) }
                            } catch (e: Exception) {
                                runOnUiThread { result.error("HASH_FAILED", e.message, null) }
                            }
                        }.start()
                    } else {
                        result.error("INVALID_ARGUMENTS", "uri must not be null", null)
                    }
                }
                "getThumbnail" -> {
                    val uriString = call.argument<String>("uri")
                    val width = call.argument<Int>("width") ?: 200
                    val height = call.argument<Int>("height") ?: 200
                    if (uriString != null) {
                        Thread {
                            try {
                                val bytes = loadPhotoThumbnail(this, uriString, width, height)
                                runOnUiThread { result.success(bytes) }
                            } catch (e: Exception) {
                                runOnUiThread { result.error("THUMBNAIL_FAILED", e.message, null) }
                            }
                        }.start()
                    } else {
                        result.error("INVALID_ARGUMENTS", "uri must not be null", null)
                    }
                }
                "getPhotoBytes" -> {
                    val uriString = call.argument<String>("uri")
                    if (uriString != null) {
                        Thread {
                            try {
                                val bytes = getPhotoBytesForUri(this, uriString)
                                runOnUiThread { result.success(bytes) }
                            } catch (e: Exception) {
                                runOnUiThread { result.error("BYTES_FAILED", e.message, null) }
                            }
                        }.start()
                    } else {
                        result.error("INVALID_ARGUMENTS", "uri must not be null", null)
                    }
                }
                else -> {
                    result.notImplemented()
                }
            }
        }
    }

    private fun scanPhotosForTrip(context: Context, startUtc: Long, finishUtc: Long): List<Map<String, Any?>> {
        val resultList = mutableListOf<Map<String, Any?>>()
        val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)

        val projection = mutableListOf(
            MediaStore.Images.Media._ID,
            MediaStore.Images.Media.DISPLAY_NAME,
            MediaStore.Images.Media.MIME_TYPE,
            MediaStore.Images.Media.DATE_TAKEN,
            MediaStore.Images.Media.DATE_ADDED,
            MediaStore.Images.Media.SIZE
        )
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            projection.add(MediaStore.Images.Media.RELATIVE_PATH)
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            projection.add(MediaStore.Images.Media.OWNER_PACKAGE_NAME)
        }

        // 1. Query by DATE_TAKEN
        val selection = "${MediaStore.Images.Media.DATE_TAKEN} >= ? AND ${MediaStore.Images.Media.DATE_TAKEN} <= ?"
        val selectionArgs = arrayOf(startUtc.toString(), finishUtc.toString())
        val sortOrder = "${MediaStore.Images.Media.DATE_TAKEN} ASC"

        queryCollection(context, collection, projection.toTypedArray(), selection, selectionArgs, sortOrder, resultList)

        // 2. Query by DATE_ADDED (for files missing DATE_TAKEN)
        // DATE_ADDED is in seconds
        val startSeconds = startUtc / 1000
        val finishSeconds = finishUtc / 1000
        val selectionAdded = "${MediaStore.Images.Media.DATE_TAKEN} IS NULL AND ${MediaStore.Images.Media.DATE_ADDED} >= ? AND ${MediaStore.Images.Media.DATE_ADDED} <= ?"
        val selectionArgsAdded = arrayOf(startSeconds.toString(), finishSeconds.toString())
        val sortOrderAdded = "${MediaStore.Images.Media.DATE_ADDED} ASC"

        queryCollection(context, collection, projection.toTypedArray(), selectionAdded, selectionArgsAdded, sortOrderAdded, resultList)

        return resultList
    }

    private fun queryCollection(
        context: Context,
        collection: Uri,
        projection: Array<String>,
        selection: String,
        selectionArgs: Array<String>,
        sortOrder: String,
        resultList: MutableList<Map<String, Any?>>
    ) {
        context.contentResolver.query(collection, projection, selection, selectionArgs, sortOrder)?.use { cursor ->
            val idCol = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
            val nameCol = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
            val mimeCol = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE)
            val dateTakenCol = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)
            val dateAddedCol = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_ADDED)
            val sizeCol = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)
            val pathCol = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH)
            } else -1
            val ownerCol = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                cursor.getColumnIndex(MediaStore.Images.Media.OWNER_PACKAGE_NAME)
            } else -1

            // Keep track of added IDs to prevent duplicate scans
            val addedIds = resultList.map { it["id"] as? Long }.filterNotNull().toSet()

            while (cursor.moveToNext()) {
                val id = cursor.getLong(idCol)
                if (addedIds.contains(id)) continue

                val uri = ContentUris.withAppendedId(collection, id)
                val displayName = cursor.getString(nameCol)
                val mimeType = cursor.getString(mimeCol)
                val dateTaken = if (cursor.isNull(dateTakenCol)) null else cursor.getLong(dateTakenCol)
                val dateAdded = if (cursor.isNull(dateAddedCol)) null else cursor.getLong(dateAddedCol) * 1000 // Convert to millis
                val size = cursor.getLong(sizeCol)
                val relativePath = if (pathCol != -1 && !cursor.isNull(pathCol)) cursor.getString(pathCol) else null
                val ownerPackageName = if (ownerCol != -1 && !cursor.isNull(ownerCol)) cursor.getString(ownerCol) else null

                // Read EXIF details
                val exifDetails = readExifForUri(context, uri)

                val photoMap = mutableMapOf<String, Any?>(
                    "id" to id,
                    "uri" to uri.toString(),
                    "displayName" to displayName,
                    "mimeType" to mimeType,
                    "dateTaken" to dateTaken,
                    "dateAdded" to dateAdded,
                    "relativePath" to relativePath,
                    "ownerPackageName" to ownerPackageName,
                    "size" to size,
                    // EXIF properties
                    "exifLatitude" to exifDetails["latitude"],
                    "exifLongitude" to exifDetails["longitude"],
                    "exifAltitude" to exifDetails["altitude"],
                    "exifMake" to exifDetails["cameraMake"],
                    "exifModel" to exifDetails["cameraModel"],
                    "exifOrientation" to exifDetails["orientation"],
                    "exifDateTaken" to exifDetails["dateTaken"]
                )

                resultList.add(photoMap)
            }
        }
    }

    private fun readExifForUri(context: Context, uri: Uri): Map<String, Any?> {
        val result = mutableMapOf<String, Any?>()
        try {
            val requireOriginalUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                try {
                    MediaStore.setRequireOriginal(uri)
                } catch (e: SecurityException) {
                    uri
                }
            } else {
                uri
            }

            context.contentResolver.openFileDescriptor(requireOriginalUri, "r")?.use { pfd ->
                val exif = ExifInterface(pfd.fileDescriptor)
                val latLong = exif.latLong
                if (latLong != null) {
                    result["latitude"] = latLong[0]
                    result["longitude"] = latLong[1]
                } else {
                    result["latitude"] = null
                    result["longitude"] = null
                }
                result["altitude"] = exif.getAltitude(Double.NaN).let { if (it.isNaN()) null else it }
                result["cameraMake"] = exif.getAttribute(ExifInterface.TAG_MAKE)
                result["cameraModel"] = exif.getAttribute(ExifInterface.TAG_MODEL)
                result["orientation"] = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)

                val dateStr = exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL)
                result["dateTaken"] = parseExifDate(dateStr)
            }
        } catch (e: Exception) {
            // Return empty exif map on error
            result["latitude"] = null
            result["longitude"] = null
            result["altitude"] = null
            result["cameraMake"] = null
            result["cameraModel"] = null
            result["orientation"] = null
            result["dateTaken"] = null
        }
        return result
    }

    private fun parseExifDate(dateStr: String?): Long? {
        if (dateStr.isNullOrBlank()) return null
        return try {
            val format = SimpleDateFormat("yyyy:MM:dd HH:mm:ss", Locale.US)
            format.timeZone = TimeZone.getDefault()
            format.parse(dateStr)?.time
        } catch (e: Exception) {
            null
        }
    }

    private fun calculateSha256ForUri(context: Context, uriString: String): String {
        val uri = Uri.parse(uriString)
        val digest = MessageDigest.getInstance("SHA-256")
        context.contentResolver.openInputStream(uri)?.use { inputStream ->
            val buffer = ByteArray(64 * 1024)
            var count: Int
            while (inputStream.read(buffer).also { count = it } > 0) {
                digest.update(buffer, 0, count)
            }
        }
        return digest.digest().joinToString("") { String.format("%02x", it) }
    }

    private fun loadPhotoThumbnail(context: Context, uriString: String, width: Int, height: Int): ByteArray {
        val uri = Uri.parse(uriString)
        val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            val size = android.util.Size(width, height)
            context.contentResolver.loadThumbnail(uri, size, null)
        } else {
            val id = ContentUris.parseId(uri)
            @Suppress("DEPRECATION")
            MediaStore.Images.Thumbnails.getThumbnail(
                context.contentResolver,
                id,
                MediaStore.Images.Thumbnails.MINI_KIND,
                null
            )
        }
        val outputStream = java.io.ByteArrayOutputStream()
        bitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, 80, outputStream)
        return outputStream.toByteArray()
    }

    private fun getPhotoBytesForUri(context: Context, uriString: String): ByteArray {
        val uri = Uri.parse(uriString)
        context.contentResolver.openInputStream(uri)?.use { inputStream ->
            return inputStream.readBytes()
        }
        throw Exception("Could not open input stream for $uriString")
    }
}


