package com.placeholder.sherpai2.domain.validation

import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.face.FaceDetection
import com.google.mlkit.vision.face.FaceDetectorOptions
import com.placeholder.sherpai2.data.local.dao.FaceModelDao
import com.placeholder.sherpai2.data.local.dao.ImageDao
import com.placeholder.sherpai2.data.local.entity.FaceModelEntity
import com.placeholder.sherpai2.data.local.entity.ImageEntity
import com.placeholder.sherpai2.ml.FaceNetModel
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.tasks.await
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton

/**
 * ValidationScanService - Quick validation scan after training
 *
 * PURPOSE: Let user verify model quality BEFORE full library scan
 *
 * STRATEGY:
 * 1. Sample 20-30 random photos with faces
 * 2. Scan for the newly trained person
 * 3. Return preview results with confidence scores
 * 4. User reviews and decides: "Looks good" or "Add more photos"
 *
 * THRESHOLD STRATEGY:
 * - Use CONSERVATIVE threshold (0.75) for validation
 * - Better to show false negatives than false positives
 * - If user approves, full scan uses slightly looser threshold (0.70)
 */
@Singleton
class ValidationScanService @Inject constructor(
    @ApplicationContext private val context: Context,
    private val imageDao: ImageDao,
    private val faceModelDao: FaceModelDao
) {

    companion object {
        private const val VALIDATION_SAMPLE_SIZE = 25
        private const val VALIDATION_THRESHOLD = 0.75f  // Conservative
    }

    /**
     * Perform validation scan after training
     *
     * @param personId The newly trained person
     * @param onProgress Callback (current, total)
     * @return Validation results with preview matches
     */
    suspend fun performValidationScan(
        personId: String,
        onProgress: (Int, Int) -> Unit = { _, _ -> }
    ): ValidationScanResult = withContext(Dispatchers.Default) {

        onProgress(0, 100)

        // Step 1: Get face model
        val faceModel = withContext(Dispatchers.IO) {
            faceModelDao.getFaceModelByPersonId(personId)
        } ?: return@withContext ValidationScanResult(
            personId = personId,
            matches = emptyList(),
            sampleSize = 0,
            errorMessage = "Face model not found"
        )

        onProgress(10, 100)

        // Step 2: Get random sample of photos with faces
        val allPhotosWithFaces = withContext(Dispatchers.IO) {
            imageDao.getImagesWithFaces()
        }

        if (allPhotosWithFaces.isEmpty()) {
            return@withContext ValidationScanResult(
                personId = personId,
                matches = emptyList(),
                sampleSize = 0,
                errorMessage = "No photos with faces in library"
            )
        }

        // Random sample
        val samplePhotos = allPhotosWithFaces.shuffled().take(VALIDATION_SAMPLE_SIZE)
        onProgress(20, 100)

        // Step 3: Scan sample photos
        val faceNetModel = FaceNetModel(context)
        val detector = FaceDetection.getClient(
            FaceDetectorOptions.Builder()
                .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
                .setMinFaceSize(0.15f)
                .build()
        )

        try {
            val matches = scanPhotosForPerson(
                photos = samplePhotos,
                faceModel = faceModel,
                faceNetModel = faceNetModel,
                detector = detector,
                threshold = VALIDATION_THRESHOLD,
                onProgress = { current, total ->
                    // Map to 20-100 range
                    val progress = 20 + (current * 80 / total)
                    onProgress(progress, 100)
                }
            )

            onProgress(100, 100)

            ValidationScanResult(
                personId = personId,
                matches = matches,
                sampleSize = samplePhotos.size,
                threshold = VALIDATION_THRESHOLD
            )

        } finally {
            faceNetModel.close()
            detector.close()
        }
    }

    /**
     * Scan photos for a specific person
     */
    private suspend fun scanPhotosForPerson(
        photos: List<ImageEntity>,
        faceModel: FaceModelEntity,
        faceNetModel: FaceNetModel,
        detector: com.google.mlkit.vision.face.FaceDetector,
        threshold: Float,
        onProgress: (Int, Int) -> Unit
    ): List<ValidationMatch> = coroutineScope {

        val modelEmbedding = faceModel.getEmbeddingArray()
        val matches = mutableListOf<ValidationMatch>()
        var processedCount = 0

        // Process in parallel
        val jobs = photos.map { photo ->
            async(Dispatchers.IO) {
                val photoMatches = scanSinglePhoto(
                    photo = photo,
                    modelEmbedding = modelEmbedding,
                    faceNetModel = faceNetModel,
                    detector = detector,
                    threshold = threshold
                )

                synchronized(matches) {
                    matches.addAll(photoMatches)
                    processedCount++
                    if (processedCount % 5 == 0) {
                        onProgress(processedCount, photos.size)
                    }
                }
            }
        }

        jobs.awaitAll()
        matches.sortedByDescending { it.confidence }
    }

    /**
     * Scan a single photo for the person
     */
    private suspend fun scanSinglePhoto(
        photo: ImageEntity,
        modelEmbedding: FloatArray,
        faceNetModel: FaceNetModel,
        detector: com.google.mlkit.vision.face.FaceDetector,
        threshold: Float
    ): List<ValidationMatch> = withContext(Dispatchers.IO) {

        try {
            // Load bitmap
            val bitmap = loadBitmapDownsampled(Uri.parse(photo.imageUri), 768)
                ?: return@withContext emptyList()

            // Detect faces
            val inputImage = InputImage.fromBitmap(bitmap, 0)
            val faces = detector.process(inputImage).await()

            // Check each face
            val matches = faces.mapNotNull { face ->
                try {
                    // Crop face
                    val faceBitmap = android.graphics.Bitmap.createBitmap(
                        bitmap,
                        face.boundingBox.left.coerceIn(0, bitmap.width - 1),
                        face.boundingBox.top.coerceIn(0, bitmap.height - 1),
                        face.boundingBox.width().coerceAtMost(bitmap.width - face.boundingBox.left),
                        face.boundingBox.height().coerceAtMost(bitmap.height - face.boundingBox.top)
                    )

                    // Generate embedding
                    val faceEmbedding = faceNetModel.generateEmbedding(faceBitmap)
                    faceBitmap.recycle()

                    // Calculate similarity
                    val similarity = faceNetModel.calculateSimilarity(faceEmbedding, modelEmbedding)

                    if (similarity >= threshold) {
                        ValidationMatch(
                            imageId = photo.imageId,
                            imageUri = photo.imageUri,
                            capturedAt = photo.capturedAt,
                            confidence = similarity,
                            boundingBox = face.boundingBox,
                            faceCount = faces.size
                        )
                    } else {
                        null
                    }
                } catch (e: Exception) {
                    null
                }
            }

            bitmap.recycle()
            matches

        } catch (e: Exception) {
            emptyList()
        }
    }

    /**
     * Load bitmap with downsampling
     */
    private fun loadBitmapDownsampled(uri: Uri, maxDim: Int): android.graphics.Bitmap? {
        return try {
            val opts = BitmapFactory.Options().apply { inJustDecodeBounds = true }
            context.contentResolver.openInputStream(uri)?.use {
                BitmapFactory.decodeStream(it, null, opts)
            }

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

            val finalOpts = BitmapFactory.Options().apply {
                inSampleSize = sample
            }

            context.contentResolver.openInputStream(uri)?.use {
                BitmapFactory.decodeStream(it, null, finalOpts)
            }
        } catch (e: Exception) {
            null
        }
    }
}

/**
 * Result of validation scan
 */
data class ValidationScanResult(
    val personId: String,
    val matches: List<ValidationMatch>,
    val sampleSize: Int,
    val threshold: Float = 0.75f,
    val errorMessage: String? = null
) {
    val matchCount: Int get() = matches.size
    val averageConfidence: Float get() = if (matches.isNotEmpty()) {
        matches.map { it.confidence }.average().toFloat()
    } else 0f

    val qualityAssessment: ValidationQuality get() = when {
        matchCount == 0 -> ValidationQuality.NO_MATCHES
        averageConfidence >= 0.85f && matchCount >= 5 -> ValidationQuality.EXCELLENT
        averageConfidence >= 0.78f && matchCount >= 3 -> ValidationQuality.GOOD
        averageConfidence < 0.75f || matchCount < 2 -> ValidationQuality.POOR
        else -> ValidationQuality.FAIR
    }
}

/**
 * Single match found during validation
 */
data class ValidationMatch(
    val imageId: String,
    val imageUri: String,
    val capturedAt: Long,
    val confidence: Float,
    val boundingBox: android.graphics.Rect,
    val faceCount: Int
)

/**
 * Overall quality assessment
 */
enum class ValidationQuality {
    EXCELLENT,   // High confidence, many matches
    GOOD,        // Decent confidence, some matches
    FAIR,        // Acceptable, proceed with caution
    POOR,        // Low confidence or very few matches
    NO_MATCHES   // No matches found at all
}