package com.placeholder.sherpai2.domain.clustering

import android.util.Log
import com.placeholder.sherpai2.data.local.dao.FaceCacheDao
import com.placeholder.sherpai2.data.local.dao.UserFeedbackDao
import com.placeholder.sherpai2.data.local.entity.FeedbackType
import com.placeholder.sherpai2.data.local.entity.UserFeedbackEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.sqrt

/**
 * ClusterRefinementService - Handle user feedback and cluster refinement
 *
 * PURPOSE:
 * ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 * Close the feedback loop between user corrections and clustering
 *
 * WORKFLOW:
 * ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 * 1. Clustering produces initial clusters
 * 2. User reviews in ValidationPreview
 * 3. User marks faces: ✅ Correct / ❌ Incorrect / ❓ Uncertain
 * 4. If too many incorrect → Call refineCluster()
 * 5. Re-cluster WITHOUT incorrect faces
 * 6. Show updated validation preview
 * 7. Repeat until user approves
 *
 * BENEFITS:
 * ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 * - Prevents bad models from being created
 * - Learns from user corrections
 * - Iterative improvement
 * - Ground truth data for future enhancements
 */
@Singleton
class ClusterRefinementService @Inject constructor(
    private val faceCacheDao: FaceCacheDao,
    private val userFeedbackDao: UserFeedbackDao,
    private val qualityAnalyzer: ClusterQualityAnalyzer
) {

    companion object {
        private const val TAG = "ClusterRefinement"

        // Thresholds for refinement decisions
        private const val MIN_REJECTION_RATIO = 0.15f  // 15% rejected → refine
        private const val MIN_CONFIRMED_FACES = 6      // Need at least 6 good faces
        private const val MAX_REFINEMENT_ITERATIONS = 3 // Prevent infinite loops
    }

    /**
     * Store user feedback for faces in a cluster
     *
     * @param cluster The cluster being reviewed
     * @param feedbackMap Map of face index → feedback type
     * @param originalConfidences Map of face index → original detection confidence
     * @return Number of feedback items stored
     */
    suspend fun storeFeedback(
        cluster: FaceCluster,
        feedbackMap: Map<DetectedFaceWithEmbedding, FeedbackType>,
        originalConfidences: Map<DetectedFaceWithEmbedding, Float> = emptyMap()
    ): Int = withContext(Dispatchers.IO) {

        val feedbackEntities = feedbackMap.map { (face, feedbackType) ->
            UserFeedbackEntity.create(
                imageId = face.imageId,
                faceIndex = 0, // We don't track faceIndex in DetectedFaceWithEmbedding yet
                clusterId = cluster.clusterId,
                personId = null, // Not created yet
                feedbackType = feedbackType,
                originalConfidence = originalConfidences[face] ?: face.confidence
            )
        }

        userFeedbackDao.insertAll(feedbackEntities)

        Log.d(TAG, "Stored ${feedbackEntities.size} feedback items for cluster ${cluster.clusterId}")
        feedbackEntities.size
    }

    /**
     * Check if cluster needs refinement based on user feedback
     *
     * Criteria:
     * - Too many rejected faces (> 15%)
     * - Too few confirmed faces (< 6)
     * - High rejection rate for cluster suggests mixed identities
     *
     * @return RefinementRecommendation with action and reason
     */
    suspend fun shouldRefineCluster(
        cluster: FaceCluster
    ): RefinementRecommendation = withContext(Dispatchers.Default) {

        val feedback = withContext(Dispatchers.IO) {
            userFeedbackDao.getFeedbackForCluster(cluster.clusterId)
        }

        if (feedback.isEmpty()) {
            return@withContext RefinementRecommendation(
                shouldRefine = false,
                reason = "No feedback provided yet"
            )
        }

        val totalFeedback = feedback.size
        val rejectedCount = feedback.count { it.getFeedbackType() == FeedbackType.REJECTED_MATCH }
        val confirmedCount = feedback.count { it.getFeedbackType() == FeedbackType.CONFIRMED_MATCH }
        val uncertainCount = feedback.count { it.getFeedbackType() == FeedbackType.UNCERTAIN }

        val rejectionRatio = rejectedCount.toFloat() / totalFeedback.toFloat()

        Log.d(TAG, "Cluster ${cluster.clusterId} feedback: " +
                "$confirmedCount confirmed, $rejectedCount rejected, $uncertainCount uncertain")

        // Check 1: Too many rejections
        if (rejectionRatio > MIN_REJECTION_RATIO) {
            return@withContext RefinementRecommendation(
                shouldRefine = true,
                reason = "High rejection rate (${(rejectionRatio * 100).toInt()}%) suggests mixed identities",
                confirmedCount = confirmedCount,
                rejectedCount = rejectedCount,
                uncertainCount = uncertainCount
            )
        }

        // Check 2: Too few confirmed faces after removing rejected
        val effectiveConfirmedCount = confirmedCount - rejectedCount
        if (effectiveConfirmedCount < MIN_CONFIRMED_FACES) {
            return@withContext RefinementRecommendation(
                shouldRefine = true,
                reason = "Only $effectiveConfirmedCount faces remain after removing rejected faces (need $MIN_CONFIRMED_FACES)",
                confirmedCount = confirmedCount,
                rejectedCount = rejectedCount,
                uncertainCount = uncertainCount
            )
        }

        // Cluster is good!
        RefinementRecommendation(
            shouldRefine = false,
            reason = "Cluster quality acceptable: $confirmedCount confirmed, $rejectedCount rejected",
            confirmedCount = confirmedCount,
            rejectedCount = rejectedCount,
            uncertainCount = uncertainCount
        )
    }

    /**
     * Refine cluster by removing rejected faces and re-clustering
     *
     * ALGORITHM:
     * 1. Get all rejected faces from feedback
     * 2. Remove those faces from cluster
     * 3. Recalculate cluster centroid
     * 4. Re-run quality analysis
     * 5. Return refined cluster
     *
     * @param cluster Original cluster to refine
     * @return Refined cluster without rejected faces
     */
    suspend fun refineCluster(
        cluster: FaceCluster,
        iterationNumber: Int = 1
    ): ClusterRefinementResult = withContext(Dispatchers.Default) {

        Log.d(TAG, "Refining cluster ${cluster.clusterId} (iteration $iterationNumber)")

        // Guard against infinite refinement
        if (iterationNumber > MAX_REFINEMENT_ITERATIONS) {
            return@withContext ClusterRefinementResult(
                success = false,
                refinedCluster = null,
                errorMessage = "Maximum refinement iterations reached. Cluster quality still poor.",
                facesRemoved = 0,
                facesRemaining = cluster.faces.size
            )
        }

        // Get rejected faces
        val feedback = withContext(Dispatchers.IO) {
            userFeedbackDao.getRejectedFacesForCluster(cluster.clusterId)
        }

        val rejectedImageIds = feedback.map { it.imageId }.toSet()

        if (rejectedImageIds.isEmpty()) {
            return@withContext ClusterRefinementResult(
                success = false,
                refinedCluster = cluster,
                errorMessage = "No rejected faces to remove",
                facesRemoved = 0,
                facesRemaining = cluster.faces.size
            )
        }

        // Remove rejected faces
        val cleanFaces = cluster.faces.filter { it.imageId !in rejectedImageIds }

        Log.d(TAG, "Removed ${rejectedImageIds.size} rejected faces, ${cleanFaces.size} remain")

        // Check if we have enough faces left
        if (cleanFaces.size < MIN_CONFIRMED_FACES) {
            return@withContext ClusterRefinementResult(
                success = false,
                refinedCluster = null,
                errorMessage = "Too few faces remaining after removing rejected faces (${cleanFaces.size} < $MIN_CONFIRMED_FACES)",
                facesRemoved = rejectedImageIds.size,
                facesRemaining = cleanFaces.size
            )
        }

        // Recalculate centroid
        val newCentroid = calculateCentroid(cleanFaces.map { it.embedding })

        // Select new representative faces
        val newRepresentatives = selectRepresentativeFacesByCentroid(cleanFaces, newCentroid, count = 6)

        // Create refined cluster
        val refinedCluster = FaceCluster(
            clusterId = cluster.clusterId,
            faces = cleanFaces,
            representativeFaces = newRepresentatives,
            photoCount = cleanFaces.map { it.imageId }.distinct().size,
            averageConfidence = cleanFaces.map { it.confidence }.average().toFloat(),
            estimatedAge = cluster.estimatedAge, // Keep same estimate
            potentialSiblings = cluster.potentialSiblings // Keep same siblings
        )

        // Re-run quality analysis
        val qualityResult = qualityAnalyzer.analyzeCluster(refinedCluster)

        Log.d(TAG, "Refined cluster quality: ${qualityResult.qualityTier} " +
                "(${qualityResult.cleanFaceCount} clean faces)")

        ClusterRefinementResult(
            success = true,
            refinedCluster = refinedCluster,
            qualityResult = qualityResult,
            facesRemoved = rejectedImageIds.size,
            facesRemaining = cleanFaces.size,
            newQualityTier = qualityResult.qualityTier
        )
    }

    /**
     * Get feedback summary for cluster
     *
     * Returns human-readable summary like:
     * "15 confirmed, 3 rejected, 2 uncertain"
     */
    suspend fun getFeedbackSummary(clusterId: Int): FeedbackSummary = withContext(Dispatchers.IO) {
        val feedback = userFeedbackDao.getFeedbackForCluster(clusterId)

        val confirmed = feedback.count { it.getFeedbackType() == FeedbackType.CONFIRMED_MATCH }
        val rejected = feedback.count { it.getFeedbackType() == FeedbackType.REJECTED_MATCH }
        val uncertain = feedback.count { it.getFeedbackType() == FeedbackType.UNCERTAIN }
        val outliers = feedback.count { it.getFeedbackType() == FeedbackType.MARKED_OUTLIER }

        FeedbackSummary(
            totalFeedback = feedback.size,
            confirmedCount = confirmed,
            rejectedCount = rejected,
            uncertainCount = uncertain,
            outlierCount = outliers,
            rejectionRatio = if (feedback.isNotEmpty()) {
                rejected.toFloat() / feedback.size.toFloat()
            } else 0f
        )
    }

    /**
     * Filter cluster to only confirmed faces
     *
     * Use Case: User has reviewed cluster, now create model using ONLY confirmed faces
     */
    suspend fun getConfirmedFaces(cluster: FaceCluster): List<DetectedFaceWithEmbedding> =
        withContext(Dispatchers.Default) {

            val confirmedFeedback = withContext(Dispatchers.IO) {
                userFeedbackDao.getConfirmedFacesForCluster(cluster.clusterId)
            }

            val confirmedImageIds = confirmedFeedback.map { it.imageId }.toSet()

            // If no explicit confirmations, assume all non-rejected faces are OK
            if (confirmedImageIds.isEmpty()) {
                val rejectedFeedback = withContext(Dispatchers.IO) {
                    userFeedbackDao.getRejectedFacesForCluster(cluster.clusterId)
                }
                val rejectedImageIds = rejectedFeedback.map { it.imageId }.toSet()

                return@withContext cluster.faces.filter { it.imageId !in rejectedImageIds }
            }

            // Return only explicitly confirmed faces
            cluster.faces.filter { it.imageId in confirmedImageIds }
        }

    /**
     * Calculate centroid from embeddings
     */
    private fun calculateCentroid(embeddings: List<FloatArray>): FloatArray {
        if (embeddings.isEmpty()) return FloatArray(0)

        val size = embeddings.first().size
        val centroid = FloatArray(size) { 0f }

        embeddings.forEach { embedding ->
            for (i in embedding.indices) {
                centroid[i] += embedding[i]
            }
        }

        val count = embeddings.size.toFloat()
        for (i in centroid.indices) {
            centroid[i] /= count
        }

        // Normalize
        val norm = sqrt(centroid.map { it * it }.sum())
        return if (norm > 0) {
            centroid.map { it / norm }.toFloatArray()
        } else {
            centroid
        }
    }

    /**
     * Select representative faces closest to centroid
     */
    private fun selectRepresentativeFacesByCentroid(
        faces: List<DetectedFaceWithEmbedding>,
        centroid: FloatArray,
        count: Int
    ): List<DetectedFaceWithEmbedding> {
        if (faces.size <= count) return faces

        val facesWithDistance = faces.map { face ->
            val similarity = cosineSimilarity(face.embedding, centroid)
            val distance = 1 - similarity
            face to distance
        }

        return facesWithDistance
            .sortedBy { it.second }
            .take(count)
            .map { it.first }
    }

    /**
     * Cosine similarity calculation
     */
    private fun cosineSimilarity(a: FloatArray, b: FloatArray): Float {
        var dotProduct = 0f
        var normA = 0f
        var normB = 0f

        for (i in a.indices) {
            dotProduct += a[i] * b[i]
            normA += a[i] * a[i]
            normB += b[i] * b[i]
        }

        return dotProduct / (sqrt(normA) * sqrt(normB))
    }
}

/**
 * Result of refinement analysis
 */
data class RefinementRecommendation(
    val shouldRefine: Boolean,
    val reason: String,
    val confirmedCount: Int = 0,
    val rejectedCount: Int = 0,
    val uncertainCount: Int = 0
)

/**
 * Result of cluster refinement
 */
data class ClusterRefinementResult(
    val success: Boolean,
    val refinedCluster: FaceCluster?,
    val qualityResult: ClusterQualityResult? = null,
    val errorMessage: String? = null,
    val facesRemoved: Int,
    val facesRemaining: Int,
    val newQualityTier: ClusterQualityTier? = null
)

/**
 * Summary of user feedback for a cluster
 */
data class FeedbackSummary(
    val totalFeedback: Int,
    val confirmedCount: Int,
    val rejectedCount: Int,
    val uncertainCount: Int,
    val outlierCount: Int,
    val rejectionRatio: Float
) {
    fun getDisplayText(): String {
        val parts = mutableListOf<String>()
        if (confirmedCount > 0) parts.add("$confirmedCount confirmed")
        if (rejectedCount > 0) parts.add("$rejectedCount rejected")
        if (uncertainCount > 0) parts.add("$uncertainCount uncertain")
        return parts.joinToString(", ")
    }
}