package com.placeholder.sherpai2.domain.usecase

import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import com.google.mlkit.vision.face.Face
import com.placeholder.sherpai2.data.local.dao.FaceCacheDao
import com.placeholder.sherpai2.data.local.dao.ImageDao
import com.placeholder.sherpai2.data.local.entity.FaceCacheEntity
import com.placeholder.sherpai2.data.local.entity.ImageEntity
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs

/**
 * PopulateFaceDetectionCache - ENHANCED VERSION
 *
 * NOW POPULATES TWO CACHES:
 * ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 * 1. ImageEntity cache (hasFaces, faceCount) - for quick filters
 * 2. FaceCacheEntity table - for Discovery pre-filtering
 *
 * SAME ML KIT SCAN - Just saves more data!
 * ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 * Previously: One scan → saves 2 fields (hasFaces, faceCount)
 * Now:        One scan → saves 2 fields + full face metadata!
 *
 * RESULT: Discovery can skip Path 3 (8 min) and use Path 2 (3 min)
 */
@Singleton
class PopulateFaceDetectionCacheUseCase @Inject constructor(
    @ApplicationContext private val context: Context,
    private val imageDao: ImageDao,
    private val faceCacheDao: FaceCacheDao
) {

    companion object {
        private const val TAG = "FaceCachePopulation"
        private const val SEMAPHORE_PERMITS = 50
        private const val BATCH_SIZE = 100
    }

    private val semaphore = Semaphore(SEMAPHORE_PERMITS)

    /**
     * ENHANCED: Populates BOTH image cache AND face metadata cache
     */
    suspend fun execute(
        onProgress: (Int, Int, String?) -> Unit = { _, _, _ -> }
    ): Int = withContext(Dispatchers.IO) {

        Log.d(TAG, "════════════════════════════════════════")
        Log.d(TAG, "Enhanced Face Cache Population Started")
        Log.d(TAG, "Populating: ImageEntity + FaceCacheEntity")
        Log.d(TAG, "════════════════════════════════════════")

        val detector = com.google.mlkit.vision.face.FaceDetection.getClient(
            com.google.mlkit.vision.face.FaceDetectorOptions.Builder()
                .setPerformanceMode(com.google.mlkit.vision.face.FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
                .setLandmarkMode(com.google.mlkit.vision.face.FaceDetectorOptions.LANDMARK_MODE_ALL)
                .setClassificationMode(com.google.mlkit.vision.face.FaceDetectorOptions.CLASSIFICATION_MODE_NONE)
                .setMinFaceSize(0.1f)
                .build()
        )

        try {
            // Get images that need face detection (hasFaces IS NULL)
            var imagesToScan = imageDao.getImagesNeedingFaceDetection()

            // CRITICAL FIX: Also check for images marked as having faces but no FaceCacheEntity
            if (imagesToScan.isEmpty()) {
                val faceStats = faceCacheDao.getCacheStats()
                if (faceStats.totalFaces == 0) {
                    // FaceCacheEntity is empty - rescan images that have faces
                    val imagesWithFaces = imageDao.getImagesWithFaces()
                    if (imagesWithFaces.isNotEmpty()) {
                        Log.w(TAG, "FaceCacheEntity empty but ${imagesWithFaces.size} images have faces - rescanning")
                        imagesToScan = imagesWithFaces
                    }
                }
            }

            if (imagesToScan.isEmpty()) {
                Log.d(TAG, "No images need scanning")
                return@withContext 0
            }

            Log.d(TAG, "Scanning ${imagesToScan.size} images")

            val total = imagesToScan.size
            val scanned = AtomicInteger(0)
            val pendingImageUpdates = mutableListOf<ImageCacheUpdate>()
            val pendingFaceCacheUpdates = mutableListOf<FaceCacheEntity>()
            val updatesMutex = Mutex()

            // Process all images in parallel
            coroutineScope {
                val jobs = imagesToScan.map { image ->
                    async(Dispatchers.Default) {
                        semaphore.acquire()
                        try {
                            processImage(image, detector)
                        } catch (e: Exception) {
                            Log.w(TAG, "Error processing ${image.imageId}: ${e.message}")
                            ScanResult(
                                ImageCacheUpdate(image.imageId, false, 0, image.imageUri),
                                emptyList()
                            )
                        } finally {
                            semaphore.release()

                            val current = scanned.incrementAndGet()
                            if (current % 50 == 0 || current == total) {
                                onProgress(current, total, image.imageUri)
                            }
                        }
                    }
                }

                // Collect results
                jobs.awaitAll().forEach { result ->
                    updatesMutex.withLock {
                        pendingImageUpdates.add(result.imageCacheUpdate)
                        pendingFaceCacheUpdates.addAll(result.faceCacheEntries)

                        // Batch write to DB
                        if (pendingImageUpdates.size >= BATCH_SIZE) {
                            flushUpdates(
                                pendingImageUpdates.toList(),
                                pendingFaceCacheUpdates.toList()
                            )
                            pendingImageUpdates.clear()
                            pendingFaceCacheUpdates.clear()
                        }
                    }
                }

                // Flush remaining
                updatesMutex.withLock {
                    if (pendingImageUpdates.isNotEmpty()) {
                        flushUpdates(pendingImageUpdates, pendingFaceCacheUpdates)
                    }
                }
            }

            val totalFacesCached = withContext(Dispatchers.IO) {
                faceCacheDao.getCacheStats().totalFaces
            }

            Log.d(TAG, "════════════════════════════════════════")
            Log.d(TAG, "Cache Population Complete!")
            Log.d(TAG, "Images scanned: ${scanned.get()}")
            Log.d(TAG, "Faces cached: $totalFacesCached")
            Log.d(TAG, "════════════════════════════════════════")

            scanned.get()
        } finally {
            detector.close()
        }
    }

    /**
     * Process a single image - detect faces and create cache entries
     */
    private suspend fun processImage(
        image: ImageEntity,
        detector: com.google.mlkit.vision.face.FaceDetector
    ): ScanResult {
        val bitmap = loadBitmapOptimized(android.net.Uri.parse(image.imageUri))
            ?: return ScanResult(
                ImageCacheUpdate(image.imageId, false, 0, image.imageUri),
                emptyList()
            )

        try {
            val inputImage = com.google.mlkit.vision.common.InputImage.fromBitmap(bitmap, 0)
            val faces = detector.process(inputImage).await()

            val imageWidth = bitmap.width
            val imageHeight = bitmap.height

            // Create ImageEntity cache update
            val imageCacheUpdate = ImageCacheUpdate(
                imageId = image.imageId,
                hasFaces = faces.isNotEmpty(),
                faceCount = faces.size,
                imageUri = image.imageUri
            )

            // Create FaceCacheEntity entries for each face (NO embeddings - generated on demand)
            val faceCacheEntries = faces.mapIndexed { index, face ->
                createFaceCacheEntry(
                    imageId = image.imageId,
                    faceIndex = index,
                    face = face,
                    imageWidth = imageWidth,
                    imageHeight = imageHeight
                )
            }

            return ScanResult(imageCacheUpdate, faceCacheEntries)

        } finally {
            bitmap.recycle()
        }
    }

    /**
     * Create FaceCacheEntity from ML Kit Face
     *
     * Uses FaceCacheEntity.create() which calculates quality metrics automatically.
     * Embeddings are NOT generated here - they're generated on-demand in Training/Discovery.
     */
    private fun createFaceCacheEntry(
        imageId: String,
        faceIndex: Int,
        face: Face,
        imageWidth: Int,
        imageHeight: Int
    ): FaceCacheEntity {
        // Determine if frontal based on head rotation
        val isFrontal = isFrontalFace(face)

        return FaceCacheEntity.create(
            imageId = imageId,
            faceIndex = faceIndex,
            boundingBox = face.boundingBox,
            imageWidth = imageWidth,
            imageHeight = imageHeight,
            confidence = 0.9f,  // High confidence from accurate detector
            isFrontal = isFrontal,
            embedding = null  // Generated on-demand in Training/Discovery
        )
    }

    /**
     * Check if face is frontal
     */
    private fun isFrontalFace(face: Face): Boolean {
        val eulerY = face.headEulerAngleY
        val eulerZ = face.headEulerAngleZ

        // Frontal if head rotation is within 20 degrees
        return abs(eulerY) <= 20f && abs(eulerZ) <= 20f
    }

    /**
     * Optimized bitmap loading
     */
    private fun loadBitmapOptimized(uri: android.net.Uri, maxDim: Int = 768): Bitmap? {
        return try {
            val options = android.graphics.BitmapFactory.Options().apply {
                inJustDecodeBounds = true
            }
            context.contentResolver.openInputStream(uri)?.use { stream ->
                android.graphics.BitmapFactory.decodeStream(stream, null, options)
            }

            var sampleSize = 1
            while (options.outWidth / sampleSize > maxDim ||
                options.outHeight / sampleSize > maxDim) {
                sampleSize *= 2
            }

            val finalOptions = android.graphics.BitmapFactory.Options().apply {
                inSampleSize = sampleSize
                inPreferredConfig = android.graphics.Bitmap.Config.ARGB_8888
            }

            context.contentResolver.openInputStream(uri)?.use { stream ->
                android.graphics.BitmapFactory.decodeStream(stream, null, finalOptions)
            }
        } catch (e: Exception) {
            Log.w(TAG, "Failed to load bitmap: ${e.message}")
            null
        }
    }

    /**
     * Batch update both caches
     */
    private suspend fun flushUpdates(
        imageUpdates: List<ImageCacheUpdate>,
        faceUpdates: List<FaceCacheEntity>
    ) = withContext(Dispatchers.IO) {
        // Update ImageEntity cache
        imageUpdates.forEach { update ->
            try {
                imageDao.updateFaceDetectionCache(
                    imageId = update.imageId,
                    hasFaces = update.hasFaces,
                    faceCount = update.faceCount,
                    timestamp = System.currentTimeMillis(),
                    version = ImageEntity.CURRENT_FACE_DETECTION_VERSION
                )
            } catch (e: Exception) {
                Log.w(TAG, "Failed to update image cache: ${e.message}")
            }
        }

        // Insert FaceCacheEntity entries
        if (faceUpdates.isNotEmpty()) {
            try {
                faceCacheDao.insertAll(faceUpdates)
            } catch (e: Exception) {
                Log.e(TAG, "Failed to insert face cache entries: ${e.message}")
            }
        }
    }

    suspend fun getUncachedImageCount(): Int = withContext(Dispatchers.IO) {
        imageDao.getImagesNeedingFaceDetectionCount()
    }

    suspend fun getCacheStats(): CacheStats = withContext(Dispatchers.IO) {
        val imageStats = imageDao.getFaceCacheStats()
        val faceStats = faceCacheDao.getCacheStats()

        // CRITICAL FIX: If ImageEntity says "scanned" but FaceCacheEntity is empty,
        // we need to re-scan. This happens after DB migration clears face_cache table.
        val imagesWithFaces = imageStats?.imagesWithFaces ?: 0
        val facesCached = faceStats.totalFaces

        // If we have images marked as having faces but no FaceCacheEntity entries,
        // those images need re-scanning
        val needsRescan = if (imagesWithFaces > 0 && facesCached == 0) {
            Log.w(TAG, "⚠️ FaceCacheEntity is empty but $imagesWithFaces images marked as having faces - forcing rescan")
            imagesWithFaces
        } else {
            imageStats?.needsScanning ?: 0
        }

        CacheStats(
            totalImages = imageStats?.totalImages ?: 0,
            imagesWithFaceCache = imageStats?.imagesWithFaceCache ?: 0,
            imagesWithFaces = imagesWithFaces,
            imagesWithoutFaces = imageStats?.imagesWithoutFaces ?: 0,
            needsScanning = needsRescan,
            totalFacesCached = facesCached,
            facesWithEmbeddings = faceStats.withEmbeddings,
            averageQuality = faceStats.avgQuality
        )
    }
}

/**
 * Result of scanning a single image
 */
private data class ScanResult(
    val imageCacheUpdate: ImageCacheUpdate,
    val faceCacheEntries: List<FaceCacheEntity>
)

/**
 * Image cache update data
 */
private data class ImageCacheUpdate(
    val imageId: String,
    val hasFaces: Boolean,
    val faceCount: Int,
    val imageUri: String
)

/**
 * Enhanced cache stats
 */
data class CacheStats(
    val totalImages: Int,
    val imagesWithFaceCache: Int,
    val imagesWithFaces: Int,
    val imagesWithoutFaces: Int,
    val needsScanning: Int,
    val totalFacesCached: Int,
    val facesWithEmbeddings: Int,
    val averageQuality: Float
) {
    val isComplete: Boolean
        get() = needsScanning == 0
}