package com.placeholder.sherpai2.domain.training

import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.util.Log
import com.placeholder.sherpai2.data.local.dao.FaceModelDao
import com.placeholder.sherpai2.data.local.dao.PersonAgeTagDao
import com.placeholder.sherpai2.data.local.dao.PersonDao
import com.placeholder.sherpai2.data.local.entity.FaceModelEntity
import com.placeholder.sherpai2.data.local.entity.PersonAgeTagEntity
import com.placeholder.sherpai2.data.local.entity.PersonEntity
import com.placeholder.sherpai2.data.local.entity.TemporalCentroid
import com.placeholder.sherpai2.domain.clustering.ClusterQualityAnalyzer
import com.placeholder.sherpai2.domain.clustering.ClusterQualityResult
import com.placeholder.sherpai2.domain.clustering.DetectedFaceWithEmbedding
import com.placeholder.sherpai2.domain.clustering.FaceCluster
import com.placeholder.sherpai2.ml.FaceNetModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs

/**
 * ClusterTrainingService - Train multi-centroid face models from clusters
 *
 * STRATEGY:
 * 1. VALIDATE cluster quality FIRST (prevent training on dirty/mixed clusters)
 * 2. For children: Create multiple temporal centroids (one per age period)
 * 3. For adults: Create single centroid (stable appearance)
 * 4. Use K-Means clustering on timestamps to find age groups
 * 5. Calculate centroid for each time period
 */
@Singleton
class ClusterTrainingService @Inject constructor(
    @ApplicationContext private val context: Context,
    private val personDao: PersonDao,
    private val faceModelDao: FaceModelDao,
    private val personAgeTagDao: PersonAgeTagDao,
    private val qualityAnalyzer: ClusterQualityAnalyzer
) {
    companion object {
        private const val TAG = "ClusterTraining"
    }

    private val faceNetModel by lazy { FaceNetModel(context) }

    /**
     * Analyze cluster quality before training
     *
     * Call this BEFORE trainFromCluster() to check if cluster is clean
     */
    suspend fun analyzeClusterQuality(cluster: FaceCluster): ClusterQualityResult {
        return qualityAnalyzer.analyzeCluster(cluster)
    }

    /**
     * Train a person from an auto-discovered cluster
     *
     * @param cluster The discovered cluster
     * @param qualityResult Optional pre-computed quality analysis (recommended)
     * @return PersonId on success
     */
    suspend fun trainFromCluster(
        cluster: FaceCluster,
        name: String,
        dateOfBirth: Long?,
        isChild: Boolean,
        siblingClusterIds: List<Int>,
        qualityResult: ClusterQualityResult? = null,
        onProgress: (Int, Int, String) -> Unit = { _, _, _ -> }
    ): String = withContext(Dispatchers.Default) {

        onProgress(0, 100, "Creating person...")

        // Step 1: Use clean faces if quality analysis was done
        val facesToUse = if (qualityResult != null && qualityResult.cleanFaces.isNotEmpty()) {
            // Use clean faces (outliers removed)
            qualityResult.cleanFaces
        } else {
            // Use all faces (legacy behavior)
            cluster.faces
        }

        if (facesToUse.size < 6) {
            throw Exception("Need at least 6 clean faces for training (have ${facesToUse.size})")
        }

        // Step 2: Create PersonEntity
        val person = PersonEntity.create(
            name = name,
            dateOfBirth = dateOfBirth,
            isChild = isChild,
            siblingIds = emptyList(), // Will update after siblings are created
            relationship = if (isChild) "Child" else null
        )

        withContext(Dispatchers.IO) {
            personDao.insert(person)
        }

        onProgress(20, 100, "Analyzing face variations...")

        // Step 3: Use pre-computed embeddings from clustering
        // CRITICAL: These embeddings are already face-specific, even in group photos!
        // The clustering phase already cropped and generated embeddings for each face.
        val facesWithEmbeddings = facesToUse.map { face ->
            Triple(
                face.imageUri,
                face.capturedAt,
                face.embedding  // ✅ Use existing embedding (already cropped to face)
            )
        }

        onProgress(50, 100, "Creating face model...")

        // Step 4: Create centroids based on whether person is a child
        val centroids = if (isChild && dateOfBirth != null) {
            createTemporalCentroidsForChild(
                facesWithEmbeddings = facesWithEmbeddings,
                dateOfBirth = dateOfBirth
            )
        } else {
            createSingleCentroid(facesWithEmbeddings)
        }

        onProgress(80, 100, "Saving model...")

        // Step 5: Calculate average confidence
        val avgConfidence = centroids.map { it.avgConfidence }.average().toFloat()

        // Step 6: Create FaceModelEntity
        val faceModel = FaceModelEntity.createFromCentroids(
            personId = person.id,
            centroids = centroids,
            trainingImageCount = facesToUse.size,
            averageConfidence = avgConfidence
        )

        withContext(Dispatchers.IO) {
            faceModelDao.insertFaceModel(faceModel)
        }

        // Step 7: Generate age tags for children
        if (isChild && dateOfBirth != null) {
            onProgress(90, 100, "Creating age tags...")
            generateAgeTags(
                personId = person.id,
                personName = name,
                faces = facesToUse,
                dateOfBirth = dateOfBirth
            )
        }

        onProgress(100, 100, "Complete!")

        person.id
    }

    /**
     * Generate PersonAgeTagEntity records for a child's photos
     *
     * Creates searchable tags like "emma_age2", "emma_age3" etc.
     * Enables queries like "Show all photos of Emma at age 2"
     */
    private suspend fun generateAgeTags(
        personId: String,
        personName: String,
        faces: List<com.placeholder.sherpai2.domain.clustering.DetectedFaceWithEmbedding>,
        dateOfBirth: Long
    ) = withContext(Dispatchers.IO) {
        try {
            val tags = faces.mapNotNull { face ->
                // Calculate age at capture
                val ageMs = face.capturedAt - dateOfBirth
                val ageYears = (ageMs / (365.25 * 24 * 60 * 60 * 1000)).toInt()

                // Skip if age is negative or unreasonably high
                if (ageYears < 0 || ageYears > 25) {
                    Log.w(TAG, "Skipping face with invalid age: $ageYears years")
                    return@mapNotNull null
                }

                PersonAgeTagEntity.create(
                    personId = personId,
                    personName = personName,
                    imageId = face.imageId,
                    ageAtCapture = ageYears,
                    confidence = 1.0f  // High confidence since this is from training data
                )
            }

            if (tags.isNotEmpty()) {
                personAgeTagDao.insertTags(tags)
                Log.d(TAG, "Created ${tags.size} age tags for $personName (ages: ${tags.map { it.ageAtCapture }.distinct().sorted()})")
            }
        } catch (e: Exception) {
            Log.e(TAG, "Failed to generate age tags", e)
            // Non-fatal - continue without tags
        }
    }

    /**
     * Create temporal centroids for a child
     * Groups faces by age and creates one centroid per age period
     */
    private fun createTemporalCentroidsForChild(
        facesWithEmbeddings: List<Triple<String, Long, FloatArray>>,
        dateOfBirth: Long
    ): List<TemporalCentroid> {

        // Group faces by age (in years)
        val facesByAge = facesWithEmbeddings.groupBy { (_, capturedAt, _) ->
            val ageMs = capturedAt - dateOfBirth
            val ageYears = (ageMs / (365.25 * 24 * 60 * 60 * 1000)).toInt()
            ageYears.coerceIn(0, 18) // Cap at 18 years
        }

        // Create one centroid per age group
        return facesByAge.map { (age, faces) ->
            val embeddings = faces.map { it.third }
            val avgEmbedding = averageEmbeddings(embeddings)
            val avgTimestamp = faces.map { it.second }.average().toLong()

            // Calculate confidence (how similar faces are to each other)
            val confidences = embeddings.map { emb ->
                cosineSimilarity(avgEmbedding, emb)
            }
            val avgConfidence = confidences.average().toFloat()

            TemporalCentroid(
                embedding = avgEmbedding.toList(),
                effectiveTimestamp = avgTimestamp,
                ageAtCapture = age.toFloat(),
                photoCount = faces.size,
                timeRangeMonths = 12, // 1 year window
                avgConfidence = avgConfidence
            )
        }.sortedBy { it.ageAtCapture }
    }

    /**
     * Create single centroid for an adult (stable appearance)
     */
    private fun createSingleCentroid(
        facesWithEmbeddings: List<Triple<String, Long, FloatArray>>
    ): List<TemporalCentroid> {

        val embeddings = facesWithEmbeddings.map { it.third }
        val avgEmbedding = averageEmbeddings(embeddings)
        val avgTimestamp = facesWithEmbeddings.map { it.second }.average().toLong()

        val confidences = embeddings.map { emb ->
            cosineSimilarity(avgEmbedding, emb)
        }
        val avgConfidence = confidences.average().toFloat()

        return listOf(
            TemporalCentroid(
                embedding = avgEmbedding.toList(),
                effectiveTimestamp = avgTimestamp,
                ageAtCapture = null,
                photoCount = facesWithEmbeddings.size,
                timeRangeMonths = 24, // 2 year window for adults
                avgConfidence = avgConfidence
            )
        )
    }

    /**
     * Average multiple embeddings into one
     */
    private fun averageEmbeddings(embeddings: List<FloatArray>): FloatArray {
        val size = embeddings.first().size
        val avg = FloatArray(size) { 0f }

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

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

        // Normalize to unit length
        val norm = kotlin.math.sqrt(avg.map { it * it }.sum())
        return avg.map { it / norm }.toFloatArray()
    }

    /**
     * Calculate cosine similarity between two embeddings
     */
    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 / (kotlin.math.sqrt(normA) * kotlin.math.sqrt(normB))
    }

    fun cleanup() {
        faceNetModel.close()
    }
}