package com.placeholder.sherpai2.ui.trainingprep

import android.app.Application
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Rect
import android.net.Uri
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
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 com.placeholder.sherpai2.domain.similarity.FaceSimilarityScorer
import com.placeholder.sherpai2.ml.FaceNetModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min

/**
 * TrainingPhotoSelectorViewModel - PREMIUM GRID + ROLLING SCAN
 *
 * FLOW:
 * 1. Start with PREMIUM faces only (solo, large, frontal, high quality)
 * 2. User picks 1-3 seed photos
 * 3. User taps "Find Similar" → navigate to RollingScanScreen
 * 4. RollingScanScreen returns with full selection
 */
@HiltViewModel
class TrainingPhotoSelectorViewModel @Inject constructor(
    application: Application,
    private val imageDao: ImageDao,
    private val faceCacheDao: FaceCacheDao,
    private val faceSimilarityScorer: FaceSimilarityScorer,
    private val faceNetModel: FaceNetModel
) : AndroidViewModel(application) {

    companion object {
        private const val TAG = "PremiumSelector"
        private const val MIN_SEEDS_FOR_ROLLING_SCAN = 1
        private const val MAX_SEEDS_FOR_ROLLING_SCAN = 5
        private const val MAX_EMBEDDINGS_TO_GENERATE = 500
    }

    // All photos (for fallback / full list)
    private var allPhotosWithFaces: List<ImageEntity> = emptyList()

    // Premium-only photos (initial view)
    private var premiumPhotos: List<ImageEntity> = emptyList()

    private val _photosWithFaces = MutableStateFlow<List<ImageEntity>>(emptyList())
    val photosWithFaces: StateFlow<List<ImageEntity>> = _photosWithFaces.asStateFlow()

    private val _selectedPhotos = MutableStateFlow<Set<ImageEntity>>(emptySet())
    val selectedPhotos: StateFlow<Set<ImageEntity>> = _selectedPhotos.asStateFlow()

    private val _isLoading = MutableStateFlow(true)
    val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()

    private val _isRanking = MutableStateFlow(false)
    val isRanking: StateFlow<Boolean> = _isRanking.asStateFlow()

    // Embedding generation progress
    private val _embeddingProgress = MutableStateFlow<EmbeddingProgress?>(null)
    val embeddingProgress: StateFlow<EmbeddingProgress?> = _embeddingProgress.asStateFlow()

    data class EmbeddingProgress(val current: Int, val total: Int)

    // Premium mode toggle
    private val _showPremiumOnly = MutableStateFlow(true)
    val showPremiumOnly: StateFlow<Boolean> = _showPremiumOnly.asStateFlow()

    // Premium face count for UI
    private val _premiumCount = MutableStateFlow(0)
    val premiumCount: StateFlow<Int> = _premiumCount.asStateFlow()

    // Can launch rolling scan?
    val canLaunchRollingScan: Boolean
        get() = _selectedPhotos.value.size in MIN_SEEDS_FOR_ROLLING_SCAN..MAX_SEEDS_FOR_ROLLING_SCAN

    // Get seed image IDs for rolling scan navigation
    fun getSeedImageIds(): List<String> = _selectedPhotos.value.map { it.imageId }

    private var rankingJob: Job? = null

    init {
        loadPremiumFaces()
    }

    /**
     * Load PREMIUM faces first (solo, large, frontal, high quality)
     * If no embeddings exist, generate them on-demand for premium candidates
     */
    private fun loadPremiumFaces() {
        viewModelScope.launch {
            try {
                _isLoading.value = true

                // First check if premium faces with embeddings exist
                var premiumFaceCache = faceCacheDao.getPremiumFaces(
                    minAreaRatio = 0.10f,
                    minQuality = 0.7f,
                    limit = 500
                )

                Log.d(TAG, "📊 Found ${premiumFaceCache.size} premium faces with embeddings")

                // If no premium faces with embeddings, generate them on-demand
                if (premiumFaceCache.isEmpty()) {
                    Log.d(TAG, "⚠️ No premium faces with embeddings - generating on-demand")

                    val candidates = faceCacheDao.getPremiumFaceCandidatesNeedingEmbeddings(
                        minAreaRatio = 0.10f,
                        minQuality = 0.7f,
                        limit = MAX_EMBEDDINGS_TO_GENERATE
                    )

                    Log.d(TAG, "📦 Found ${candidates.size} premium candidates needing embeddings")

                    if (candidates.isNotEmpty()) {
                        generateEmbeddingsForCandidates(candidates)

                        // Re-query after generating
                        premiumFaceCache = faceCacheDao.getPremiumFaces(
                            minAreaRatio = 0.10f,
                            minQuality = 0.7f,
                            limit = 500
                        )
                        Log.d(TAG, "✅ After generation: ${premiumFaceCache.size} premium faces")
                    }
                }

                _premiumCount.value = premiumFaceCache.size

                // Get corresponding ImageEntities
                val premiumImageIds = premiumFaceCache.map { it.imageId }.distinct()
                val images = imageDao.getImagesByIds(premiumImageIds)

                // Sort by quality (highest first)
                val imageQualityMap = premiumFaceCache.associate { it.imageId to it.qualityScore }
                premiumPhotos = images.sortedByDescending { imageQualityMap[it.imageId] ?: 0f }

                _photosWithFaces.value = premiumPhotos

                // Also load all photos for fallback
                allPhotosWithFaces = imageDao.getImagesWithFaces()
                    .sortedBy { it.faceCount ?: 999 }

                Log.d(TAG, "✅ Premium: ${premiumPhotos.size}, Total: ${allPhotosWithFaces.size}")

            } catch (e: Exception) {
                Log.e(TAG, "❌ Failed to load premium faces", e)
                // Fallback to all faces
                loadAllFaces()
            } finally {
                _isLoading.value = false
                _embeddingProgress.value = null
            }
        }
    }

    /**
     * Generate embeddings for premium face candidates
     */
    private suspend fun generateEmbeddingsForCandidates(candidates: List<FaceCacheEntity>) {
        val context = getApplication<Application>()
        val total = candidates.size
        var processed = 0

        withContext(Dispatchers.IO) {
            // Get image URIs for candidates
            val imageIds = candidates.map { it.imageId }.distinct()
            val images = imageDao.getImagesByIds(imageIds)
            val imageUriMap = images.associate { it.imageId to it.imageUri }

            for (candidate in candidates) {
                try {
                    val imageUri = imageUriMap[candidate.imageId] ?: continue

                    // Load bitmap
                    val bitmap = loadBitmapOptimized(context, Uri.parse(imageUri)) ?: continue

                    // Crop face
                    val croppedFace = cropFaceWithPadding(bitmap, candidate.getBoundingBox())
                    bitmap.recycle()

                    if (croppedFace == null) continue

                    // Generate embedding
                    val embedding = faceNetModel.generateEmbedding(croppedFace)
                    croppedFace.recycle()

                    // Validate embedding
                    if (embedding.any { it != 0f }) {
                        // Save to database
                        val embeddingJson = FaceCacheEntity.embeddingToJson(embedding)
                        faceCacheDao.updateEmbedding(candidate.imageId, candidate.faceIndex, embeddingJson)
                    }

                } catch (e: Exception) {
                    Log.w(TAG, "Failed to generate embedding for ${candidate.imageId}: ${e.message}")
                }

                processed++
                withContext(Dispatchers.Main) {
                    _embeddingProgress.value = EmbeddingProgress(processed, total)
                }
            }
        }

        Log.d(TAG, "✅ Generated embeddings for $processed/$total candidates")
    }

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

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

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

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

    private fun cropFaceWithPadding(bitmap: Bitmap, boundingBox: Rect): Bitmap? {
        return try {
            val padding = (max(boundingBox.width(), boundingBox.height()) * 0.25f).toInt()
            val left = max(0, boundingBox.left - padding)
            val top = max(0, boundingBox.top - padding)
            val right = min(bitmap.width, boundingBox.right + padding)
            val bottom = min(bitmap.height, boundingBox.bottom + padding)
            val width = right - left
            val height = bottom - top

            if (width > 0 && height > 0) {
                Bitmap.createBitmap(bitmap, left, top, width, height)
            } else null
        } catch (e: Exception) {
            Log.w(TAG, "Failed to crop face: ${e.message}")
            null
        }
    }

    /**
     * Fallback: load all photos with faces
     */
    private suspend fun loadAllFaces() {
        try {
            val photos = imageDao.getImagesWithFaces()
            allPhotosWithFaces = photos.sortedBy { it.faceCount ?: 999 }
            premiumPhotos = allPhotosWithFaces.filter { it.faceCount == 1 }.take(200)
            _photosWithFaces.value = if (_showPremiumOnly.value) premiumPhotos else allPhotosWithFaces
            Log.d(TAG, "✅ Fallback loaded ${allPhotosWithFaces.size} photos")
        } catch (e: Exception) {
            Log.e(TAG, "❌ Failed fallback load", e)
            allPhotosWithFaces = emptyList()
            premiumPhotos = emptyList()
            _photosWithFaces.value = emptyList()
        }
    }

    /**
     * Toggle between premium-only and all photos
     */
    fun togglePremiumOnly() {
        _showPremiumOnly.value = !_showPremiumOnly.value
        _photosWithFaces.value = if (_showPremiumOnly.value) premiumPhotos else allPhotosWithFaces
        Log.d(TAG, "📊 Showing ${if (_showPremiumOnly.value) "premium only" else "all photos"}")
    }

    fun toggleSelection(photo: ImageEntity) {
        val current = _selectedPhotos.value.toMutableSet()

        if (photo in current) {
            current.remove(photo)
            Log.d(TAG, "➖ Deselected photo: ${photo.imageId}")
        } else {
            current.add(photo)
            Log.d(TAG, "➕ Selected photo: ${photo.imageId}")
        }

        _selectedPhotos.value = current
        Log.d(TAG, "📊 Total selected: ${current.size}")

        // Trigger ranking
        triggerLiveRanking()
    }

    private fun triggerLiveRanking() {
        Log.d(TAG, "🔄 triggerLiveRanking() called")

        // Cancel previous ranking job
        rankingJob?.cancel()

        val selectedCount = _selectedPhotos.value.size

        if (selectedCount == 0) {
            Log.d(TAG, "⏹️ No photos selected, resetting to original order")
            _photosWithFaces.value = allPhotosWithFaces
            _isRanking.value = false
            return
        }

        Log.d(TAG, "⏳ Starting debounced ranking (300ms delay)...")

        // Debounce ranking by 300ms
        rankingJob = viewModelScope.launch {
            try {
                delay(300)
                Log.d(TAG, "✓ Debounce complete, starting ranking...")

                _isRanking.value = true

                // Get embeddings for selected photos
                val selectedImageIds = _selectedPhotos.value.map { it.imageId }
                Log.d(TAG, "📥 Getting embeddings for ${selectedImageIds.size} selected photos...")

                val selectedEmbeddings = faceCacheDao.getEmbeddingsForImages(selectedImageIds)
                    .mapNotNull { it.getEmbedding() }

                Log.d(TAG, "📦 Retrieved ${selectedEmbeddings.size} embeddings")

                if (selectedEmbeddings.isEmpty()) {
                    Log.w(TAG, "⚠️ No embeddings available! Check if face cache is populated.")
                    _photosWithFaces.value = allPhotosWithFaces
                    return@launch
                }

                // Calculate centroid
                Log.d(TAG, "🧮 Calculating centroid from ${selectedEmbeddings.size} embeddings...")
                val centroidStart = System.currentTimeMillis()
                val centroid = faceSimilarityScorer.calculateCentroid(selectedEmbeddings)
                val centroidTime = System.currentTimeMillis() - centroidStart
                Log.d(TAG, "✓ Centroid calculated in ${centroidTime}ms")

                // Score all photos
                val allImageIds = allPhotosWithFaces.map { it.imageId }
                Log.d(TAG, "🎯 Scoring ${allImageIds.size} photos against centroid...")

                val scoringStart = System.currentTimeMillis()
                val scoredPhotos = faceSimilarityScorer.scorePhotosAgainstCentroid(
                    allImageIds = allImageIds,
                    selectedImageIds = selectedImageIds.toSet(),
                    centroid = centroid
                )
                val scoringTime = System.currentTimeMillis() - scoringStart
                Log.d(TAG, "✓ Scoring completed in ${scoringTime}ms")
                Log.d(TAG, "📊 Scored ${scoredPhotos.size} photos")

                // Create score map
                val scoreMap = scoredPhotos.associate { it.imageId to it.finalScore }

                // Log top 5 scores for debugging
                val top5 = scoredPhotos.take(5)
                top5.forEach { scored ->
                    Log.d(TAG, "  🏆 Top photo: ${scored.imageId.take(8)} - score: ${scored.finalScore}")
                }

                // Re-rank photos
                val rankingStart = System.currentTimeMillis()
                val rankedPhotos = allPhotosWithFaces.sortedByDescending { photo ->
                    if (photo in _selectedPhotos.value) {
                        1.0f // Selected photos stay at top
                    } else {
                        scoreMap[photo.imageId] ?: 0f
                    }
                }
                val rankingTime = System.currentTimeMillis() - rankingStart
                Log.d(TAG, "✓ Ranking completed in ${rankingTime}ms")

                // Update UI
                _photosWithFaces.value = rankedPhotos

                val totalTime = centroidTime + scoringTime + rankingTime
                Log.d(TAG, "🎉 Live ranking complete! Total time: ${totalTime}ms")
                Log.d(TAG, "   - Centroid: ${centroidTime}ms")
                Log.d(TAG, "   - Scoring: ${scoringTime}ms")
                Log.d(TAG, "   - Ranking: ${rankingTime}ms")

            } catch (e: Exception) {
                Log.e(TAG, "❌ Ranking failed!", e)
                Log.e(TAG, "   Error: ${e.message}")
                Log.e(TAG, "   Stack: ${e.stackTraceToString()}")
            } finally {
                _isRanking.value = false
            }
        }
    }

    fun clearSelection() {
        Log.d(TAG, "🗑️ Clearing selection")
        _selectedPhotos.value = emptySet()
        _photosWithFaces.value = allPhotosWithFaces
        _isRanking.value = false
        rankingJob?.cancel()
    }

    fun autoSelect(count: Int = 25) {
        val photos = allPhotosWithFaces.take(count)
        _selectedPhotos.value = photos.toSet()
        Log.d(TAG, "🤖 Auto-selected ${photos.size} photos")
        triggerLiveRanking()
    }

    fun selectSingleFacePhotos(count: Int = 25) {
        val singleFacePhotos = allPhotosWithFaces
            .filter { it.faceCount == 1 }
            .take(count)
        _selectedPhotos.value = singleFacePhotos.toSet()
        Log.d(TAG, "👤 Selected ${singleFacePhotos.size} single-face photos")
        triggerLiveRanking()
    }

    fun refresh() {
        Log.d(TAG, "🔄 Refreshing data")
        loadPremiumFaces()
    }

    override fun onCleared() {
        super.onCleared()
        Log.d(TAG, "🧹 ViewModel cleared")
        rankingJob?.cancel()
    }
}