package com.placeholder.sherpai2.domain.clustering

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.util.Log
import com.google.android.gms.tasks.Tasks
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.FaceCacheDao
import com.placeholder.sherpai2.data.local.dao.ImageDao
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.sync.Semaphore
import kotlinx.coroutines.withContext
import java.util.Calendar
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.sqrt
import kotlin.random.Random

/**
 * TemporalClusteringService - Year-based clustering with intelligent child detection
 *
 * STRATEGY:
 * ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 * 1. Process ALL photos (no limits)
 * 2. Apply strict quality filter (FaceQualityFilter)
 * 3. Group faces by YEAR
 * 4. Cluster within each year
 * 5. Link clusters across years (same person)
 * 6. Detect children (changing appearance over years)
 * 7. Generate tags: "Emma_2020", "Emma_Age_2", "Brad_Pitt"
 *
 * CHILD DETECTION:
 * ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 * A person is a CHILD if:
 * - Appears across 3+ years
 * - Face embeddings change significantly between years (>0.20 distance)
 * - Consistent presence (not just random appearances)
 *
 * OUTPUT:
 * ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 * Adults: "Brad_Pitt" (single cluster)
 * Children: "Emma_2020", "Emma_2021", "Emma_2022" (yearly clusters)
 *           OR "Emma_Age_2", "Emma_Age_3", "Emma_Age_4" (if DOB known)
 */
@Singleton
class TemporalClusteringService @Inject constructor(
    @ApplicationContext private val context: Context,
    private val imageDao: ImageDao,
    private val faceCacheDao: FaceCacheDao
) {

    private val semaphore = Semaphore(8)
    private val deterministicRandom = Random(42)

    companion object {
        private const val TAG = "TemporalClustering"
        private const val CHILD_EMBEDDING_DRIFT_THRESHOLD = 0.20f // Significant change
        private const val CHILD_MIN_YEARS = 3 // Must span 3+ years
        private const val ADULT_SIMILARITY_THRESHOLD = 0.80f // 80% similar across years
        private const val CHILD_SIMILARITY_THRESHOLD = 0.70f // 70% similar (more lenient)
    }

    /**
     * Discover people with year-based clustering
     *
     * @return List of AnnotatedCluster (year-specific clusters with metadata)
     */
    suspend fun discoverPeopleByYear(
        onProgress: (Int, Int, String) -> Unit = { _, _, _ -> }
    ): TemporalClusteringResult = withContext(Dispatchers.Default) {

        val startTime = System.currentTimeMillis()

        onProgress(5, 100, "Loading all photos...")

        // STEP 1: Load ALL images (no limit)
        val allImages = withContext(Dispatchers.IO) {
            imageDao.getAllImages()
        }

        if (allImages.isEmpty()) {
            return@withContext TemporalClusteringResult(
                clusters = emptyList(),
                totalPhotosProcessed = 0,
                totalFacesDetected = 0,
                processingTimeMs = 0,
                errorMessage = "No photos in library"
            )
        }

        Log.d(TAG, "Processing ${allImages.size} photos (no limit)")

        onProgress(10, 100, "Detecting high-quality faces...")

        // STEP 2: Detect faces with STRICT quality filtering
        val faceNetModel = FaceNetModel(context)
        val detector = FaceDetection.getClient(
            FaceDetectorOptions.Builder()
                .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
                .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
                .setMinFaceSize(0.15f)
                .build()
        )

        try {
            val allFaces = mutableListOf<DetectedFaceWithEmbedding>()

            coroutineScope {
                val jobs = allImages.mapIndexed { index, image ->
                    async(Dispatchers.IO) {
                        semaphore.acquire()
                        try {
                            val bitmap = loadBitmapDownsampled(Uri.parse(image.imageUri), 768)
                                ?: return@async emptyList()

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

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

                            val validFaces = faces.mapNotNull { face ->
                                // Apply STRICT quality filter
                                val qualityCheck = FaceQualityFilter.validateForDiscovery(
                                    face = face,
                                    imageWidth = imageWidth,
                                    imageHeight = imageHeight
                                )

                                if (!qualityCheck.isValid) {
                                    return@mapNotNull null
                                }

                                // Only process SOLO photos (faceCount == 1)
                                if (faces.size != 1) {
                                    return@mapNotNull null
                                }

                                try {
                                    val faceBitmap = 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)
                                    )

                                    val embedding = faceNetModel.generateEmbedding(faceBitmap)
                                    faceBitmap.recycle()

                                    DetectedFaceWithEmbedding(
                                        imageId = image.imageId,
                                        imageUri = image.imageUri,
                                        capturedAt = image.capturedAt,
                                        embedding = embedding,
                                        boundingBox = face.boundingBox,
                                        confidence = qualityCheck.confidenceScore,
                                        faceCount = 1,
                                        imageWidth = imageWidth,
                                        imageHeight = imageHeight
                                    )
                                } catch (e: Exception) {
                                    null
                                }
                            }

                            bitmap.recycle()

                            if (index % 50 == 0) {
                                val progress = 10 + (index * 40 / allImages.size)
                                onProgress(progress, 100, "Processed $index/${allImages.size} photos...")
                            }

                            validFaces
                        } finally {
                            semaphore.release()
                        }
                    }
                }

                jobs.awaitAll().flatten().forEach { allFaces.add(it) }
            }

            Log.d(TAG, "Detected ${allFaces.size} high-quality solo faces")

            if (allFaces.isEmpty()) {
                return@withContext TemporalClusteringResult(
                    clusters = emptyList(),
                    totalPhotosProcessed = allImages.size,
                    totalFacesDetected = 0,
                    processingTimeMs = System.currentTimeMillis() - startTime,
                    errorMessage = "No high-quality solo faces found"
                )
            }

            onProgress(50, 100, "Grouping faces by year...")

            // STEP 3: Group faces by YEAR
            val facesByYear = groupFacesByYear(allFaces)

            Log.d(TAG, "Faces grouped into ${facesByYear.size} years")

            onProgress(60, 100, "Clustering within each year...")

            // STEP 4: Cluster within each year
            val yearClusters = mutableListOf<YearCluster>()

            facesByYear.forEach { (year, faces) ->
                Log.d(TAG, "Clustering $year: ${faces.size} faces")

                val rawClusters = performDBSCAN(
                    faces = faces,
                    epsilon = 0.24f,
                    minPoints = 3
                )

                rawClusters.forEach { rawCluster ->
                    yearClusters.add(
                        YearCluster(
                            year = year,
                            faces = rawCluster.faces,
                            centroid = calculateCentroid(rawCluster.faces.map { it.embedding })
                        )
                    )
                }
            }

            Log.d(TAG, "Created ${yearClusters.size} year-specific clusters")

            onProgress(80, 100, "Linking clusters across years...")

            // STEP 5: Link clusters across years (detect same person)
            val personGroups = linkClustersAcrossYears(yearClusters)

            Log.d(TAG, "Identified ${personGroups.size} unique people")

            onProgress(90, 100, "Detecting children and generating tags...")

            // STEP 6: Detect children and generate final clusters
            val annotatedClusters = personGroups.flatMap { group ->
                annotatePersonGroup(group)
            }

            onProgress(100, 100, "Complete!")

            TemporalClusteringResult(
                clusters = annotatedClusters.sortedByDescending { it.cluster.faces.size },
                totalPhotosProcessed = allImages.size,
                totalFacesDetected = allFaces.size,
                processingTimeMs = System.currentTimeMillis() - startTime
            )

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

    /**
     * Group faces by year of capture
     */
    private fun groupFacesByYear(faces: List<DetectedFaceWithEmbedding>): Map<String, List<DetectedFaceWithEmbedding>> {
        return faces.groupBy { face ->
            val calendar = Calendar.getInstance()
            calendar.timeInMillis = face.capturedAt
            calendar.get(Calendar.YEAR).toString()
        }
    }

    /**
     * Link year clusters that belong to the same person
     */
    private fun linkClustersAcrossYears(yearClusters: List<YearCluster>): List<PersonGroup> {
        val sortedClusters = yearClusters.sortedBy { it.year }
        val visited = mutableSetOf<YearCluster>()
        val personGroups = mutableListOf<PersonGroup>()

        for (cluster in sortedClusters) {
            if (cluster in visited) continue

            val group = mutableListOf<YearCluster>()
            group.add(cluster)
            visited.add(cluster)

            // Find similar clusters in subsequent years
            for (otherCluster in sortedClusters) {
                if (otherCluster in visited) continue
                if (otherCluster.year <= cluster.year) continue

                val similarity = cosineSimilarity(cluster.centroid, otherCluster.centroid)

                // Use adaptive threshold based on year gap
                val yearGap = otherCluster.year.toInt() - cluster.year.toInt()
                val threshold = if (yearGap <= 2) {
                    ADULT_SIMILARITY_THRESHOLD
                } else {
                    CHILD_SIMILARITY_THRESHOLD // More lenient for children
                }

                if (similarity >= threshold) {
                    group.add(otherCluster)
                    visited.add(otherCluster)
                }
            }

            personGroups.add(PersonGroup(clusters = group))
        }

        return personGroups
    }

    /**
     * Annotate person group (detect if child, generate tags)
     */
    private fun annotatePersonGroup(group: PersonGroup): List<AnnotatedCluster> {
        val sortedClusters = group.clusters.sortedBy { it.year }

        // Detect if this is a child
        val isChild = detectChild(sortedClusters)

        return if (isChild) {
            // Child: Create separate cluster for each year
            sortedClusters.map { yearCluster ->
                AnnotatedCluster(
                    cluster = FaceCluster(
                        clusterId = 0,
                        faces = yearCluster.faces,
                        representativeFaces = selectRepresentativeFaces(yearCluster.faces, 6),
                        photoCount = yearCluster.faces.size,
                        averageConfidence = yearCluster.faces.map { it.confidence }.average().toFloat(),
                        estimatedAge = AgeEstimate.CHILD,
                        potentialSiblings = emptyList()
                    ),
                    year = yearCluster.year,
                    isChild = true,
                    suggestedName = null,
                    suggestedAge = estimateAgeInYear(yearCluster.year, sortedClusters)
                )
            }
        } else {
            // Adult: Single cluster combining all years
            val allFaces = sortedClusters.flatMap { it.faces }
            listOf(
                AnnotatedCluster(
                    cluster = FaceCluster(
                        clusterId = 0,
                        faces = allFaces,
                        representativeFaces = selectRepresentativeFaces(allFaces, 6),
                        photoCount = allFaces.size,
                        averageConfidence = allFaces.map { it.confidence }.average().toFloat(),
                        estimatedAge = AgeEstimate.ADULT,
                        potentialSiblings = emptyList()
                    ),
                    year = "All Years",
                    isChild = false,
                    suggestedName = null,
                    suggestedAge = null
                )
            )
        }
    }

    /**
     * Detect if person group represents a child
     */
    private fun detectChild(clusters: List<YearCluster>): Boolean {
        if (clusters.size < CHILD_MIN_YEARS) {
            return false // Need 3+ years to detect child
        }

        // Calculate embedding drift between first and last year
        val firstCentroid = clusters.first().centroid
        val lastCentroid = clusters.last().centroid
        val drift = 1 - cosineSimilarity(firstCentroid, lastCentroid)

        // If embeddings changed significantly, likely a child
        return drift >= CHILD_EMBEDDING_DRIFT_THRESHOLD
    }

    /**
     * Estimate age in specific year based on cluster position
     */
    private fun estimateAgeInYear(targetYear: String, allClusters: List<YearCluster>): Int? {
        val sortedClusters = allClusters.sortedBy { it.year }
        val firstYear = sortedClusters.first().year.toInt()
        val targetYearInt = targetYear.toInt()

        val yearsSinceFirst = targetYearInt - firstYear
        return yearsSinceFirst + 1 // Start at age 1
    }

    /**
     * Select representative faces
     */
    private fun selectRepresentativeFaces(
        faces: List<DetectedFaceWithEmbedding>,
        count: Int
    ): List<DetectedFaceWithEmbedding> {
        if (faces.size <= count) return faces

        val centroid = calculateCentroid(faces.map { it.embedding })

        return faces
            .map { face -> face to (1 - cosineSimilarity(face.embedding, centroid)) }
            .sortedBy { it.second }
            .take(count)
            .map { it.first }
    }

    /**
     * DBSCAN clustering
     */
    private fun performDBSCAN(
        faces: List<DetectedFaceWithEmbedding>,
        epsilon: Float,
        minPoints: Int
    ): List<RawCluster> {
        val visited = mutableSetOf<Int>()
        val clusters = mutableListOf<RawCluster>()
        var clusterId = 0

        for (i in faces.indices) {
            if (i in visited) continue

            val neighbors = findNeighbors(i, faces, epsilon)

            if (neighbors.size < minPoints) {
                visited.add(i)
                continue
            }

            val cluster = mutableListOf<DetectedFaceWithEmbedding>()
            val queue = ArrayDeque(neighbors)

            while (queue.isNotEmpty()) {
                val pointIdx = queue.removeFirst()
                if (pointIdx in visited) continue

                visited.add(pointIdx)
                cluster.add(faces[pointIdx])

                val pointNeighbors = findNeighbors(pointIdx, faces, epsilon)
                if (pointNeighbors.size >= minPoints) {
                    queue.addAll(pointNeighbors.filter { it !in visited })
                }
            }

            if (cluster.size >= minPoints) {
                clusters.add(RawCluster(clusterId++, cluster))
            }
        }

        return clusters
    }

    private fun findNeighbors(
        pointIdx: Int,
        faces: List<DetectedFaceWithEmbedding>,
        epsilon: Float
    ): List<Int> {
        val point = faces[pointIdx]
        return faces.indices.filter { i ->
            if (i == pointIdx) return@filter false
            val similarity = cosineSimilarity(point.embedding, faces[i].embedding)
            similarity > (1 - epsilon)
        }
    }

    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))
    }

    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
        }

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

        return centroid
    }

    private fun loadBitmapDownsampled(uri: Uri, maxDim: Int): 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
                inPreferredConfig = Bitmap.Config.RGB_565
            }

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

/**
 * Year-specific cluster
 */
data class YearCluster(
    val year: String,
    val faces: List<DetectedFaceWithEmbedding>,
    val centroid: FloatArray
)

/**
 * Group of year clusters belonging to same person
 */
data class PersonGroup(
    val clusters: List<YearCluster>
)

/**
 * Annotated cluster with temporal metadata
 */
data class AnnotatedCluster(
    val cluster: FaceCluster,
    val year: String,
    val isChild: Boolean,
    val suggestedName: String?,
    val suggestedAge: Int?
) {
    /**
     * Generate tag for this cluster
     * Examples:
     * - Child: "Emma_2020" or "Emma_Age_2"
     * - Adult: "Brad_Pitt"
     */
    fun generateTag(name: String): String {
        return if (isChild) {
            if (suggestedAge != null) {
                "${name}_Age_${suggestedAge}"
            } else {
                "${name}_${year}"
            }
        } else {
            name
        }
    }
}

/**
 * Result of temporal clustering
 */
data class TemporalClusteringResult(
    val clusters: List<AnnotatedCluster>,
    val totalPhotosProcessed: Int,
    val totalFacesDetected: Int,
    val processingTimeMs: Long,
    val errorMessage: String? = null
)