package com.placeholder.sherpai2.data.service

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import com.placeholder.sherpai2.data.local.dao.ImageTagDao
import com.placeholder.sherpai2.data.local.dao.PersonDao
import com.placeholder.sherpai2.data.local.dao.PhotoFaceTagDao
import com.placeholder.sherpai2.data.local.dao.TagDao
import com.placeholder.sherpai2.data.local.entity.ImageEntity
import com.placeholder.sherpai2.data.local.entity.ImageTagEntity
import com.placeholder.sherpai2.data.local.entity.TagEntity
import com.placeholder.sherpai2.data.repository.DetectedFace
import com.placeholder.sherpai2.util.DiagnosticLogger
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Calendar
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs

/**
 * AutoTaggingService - Intelligent auto-tagging system
 *
 * Capabilities:
 * - Face-based tags (group_photo, selfie, couple)
 * - Scene tags (portrait, landscape, square orientation)
 * - Time tags (morning, afternoon, evening, night)
 * - Quality tags (high_res, low_res)
 * - Relationship tags (family, friend, colleague from PersonEntity)
 * - Birthday tags (from PersonEntity DOB)
 * - Indoor/Outdoor estimation (basic heuristic)
 */
@Singleton
class AutoTaggingService @Inject constructor(
    @ApplicationContext private val context: Context,
    private val tagDao: TagDao,
    private val imageTagDao: ImageTagDao,
    private val photoFaceTagDao: PhotoFaceTagDao,
    private val personDao: PersonDao
) {

    // ======================
    // MAIN AUTO-TAGGING
    // ======================

    /**
     * Auto-tag an image with all applicable system tags
     *
     * @return Number of tags applied
     */
    suspend fun autoTagImage(
        imageEntity: ImageEntity,
        bitmap: Bitmap,
        detectedFaces: List<DetectedFace>
    ): Int = withContext(Dispatchers.Default) {
        val tagsToApply = mutableListOf<String>()

        // Face-count based tags
        when (detectedFaces.size) {
            0 -> { /* No face tags */ }
            1 -> {
                if (isSelfie(detectedFaces[0], bitmap)) {
                    tagsToApply.add("selfie")
                } else {
                    tagsToApply.add("single_person")
                }
            }
            2 -> tagsToApply.add("couple")
            in 3..5 -> tagsToApply.add("group_photo")
            in 6..10 -> {
                tagsToApply.add("group_photo")
                tagsToApply.add("large_group")
            }
            else -> {
                tagsToApply.add("group_photo")
                tagsToApply.add("large_group")
                tagsToApply.add("crowd")
            }
        }

        // Orientation tags
        val aspectRatio = bitmap.width.toFloat() / bitmap.height.toFloat()
        when {
            aspectRatio > 1.3f -> tagsToApply.add("landscape")
            aspectRatio < 0.77f -> tagsToApply.add("portrait")
            else -> tagsToApply.add("square")
        }

        // Resolution tags
        val megapixels = (bitmap.width * bitmap.height) / 1_000_000f
        when {
            megapixels > 2.0f -> tagsToApply.add("high_res")
            megapixels < 0.5f -> tagsToApply.add("low_res")
        }

        // Time-based tags
        val hourOfDay = getHourFromTimestamp(imageEntity.capturedAt)
        tagsToApply.add(when (hourOfDay) {
            in 5..10 -> "morning"
            in 11..16 -> "afternoon"
            in 17..20 -> "evening"
            else -> "night"
        })

        // Indoor/Outdoor estimation (only if image is large enough)
        if (bitmap.width >= 200 && bitmap.height >= 200) {
            val isIndoor = estimateIndoorOutdoor(bitmap)
            tagsToApply.add(if (isIndoor) "indoor" else "outdoor")
        }

        // Apply all tags
        var tagsApplied = 0
        tagsToApply.forEach { tagName ->
            if (applySystemTag(imageEntity.imageId, tagName)) {
                tagsApplied++
            }
        }

        DiagnosticLogger.d("AutoTag: Applied $tagsApplied tags to image ${imageEntity.imageId}")
        tagsApplied
    }

    // ======================
    // RELATIONSHIP TAGS
    // ======================

    /**
     * Tag all images with a person using their relationship tag
     *
     * @param personId Person to tag images for
     * @return Number of tags applied
     */
    suspend fun autoTagRelationshipForPerson(personId: String): Int = withContext(Dispatchers.IO) {
        val person = personDao.getPersonById(personId) ?: return@withContext 0
        val relationship = person.relationship?.lowercase() ?: return@withContext 0

        // Get face model for this person
        val faceModels = photoFaceTagDao.getAllTagsForFaceModel(personId)
        if (faceModels.isEmpty()) return@withContext 0

        val imageIds = faceModels.map { it.imageId }.distinct()

        var tagsApplied = 0
        imageIds.forEach { imageId ->
            if (applySystemTag(imageId, relationship)) {
                tagsApplied++
            }
        }

        DiagnosticLogger.i("AutoTag: Applied '$relationship' tag to $tagsApplied images for ${person.name}")
        tagsApplied
    }

    /**
     * Tag relationships for ALL persons in database
     */
    suspend fun autoTagAllRelationships(): Int = withContext(Dispatchers.IO) {
        val persons = personDao.getAllPersons()
        var totalTags = 0

        persons.forEach { person ->
            totalTags += autoTagRelationshipForPerson(person.id)
        }

        DiagnosticLogger.i("AutoTag: Applied $totalTags relationship tags across ${persons.size} persons")
        totalTags
    }

    // ======================
    // BIRTHDAY TAGS
    // ======================

    /**
     * Tag images near a person's birthday
     *
     * @param personId Person whose birthday to check
     * @param daysRange Days before/after birthday to consider (default: 3)
     * @return Number of tags applied
     */
    suspend fun autoTagBirthdaysForPerson(
        personId: String,
        daysRange: Int = 3
    ): Int = withContext(Dispatchers.IO) {
        val person = personDao.getPersonById(personId) ?: return@withContext 0
        val dateOfBirth = person.dateOfBirth ?: return@withContext 0

        // Get all face tags for this person
        val faceTags = photoFaceTagDao.getAllTagsForFaceModel(personId)
        if (faceTags.isEmpty()) return@withContext 0

        var tagsApplied = 0

        faceTags.forEach { faceTag ->
            // Get the image to check its timestamp
            val imageId = faceTag.imageId

            // Check if image was captured near birthday
            if (isNearBirthday(faceTag.detectedAt, dateOfBirth, daysRange)) {
                if (applySystemTag(imageId, "birthday")) {
                    tagsApplied++
                }
            }
        }

        DiagnosticLogger.i("AutoTag: Applied 'birthday' tag to $tagsApplied images for ${person.name}")
        tagsApplied
    }

    /**
     * Tag birthdays for ALL persons with DOB
     */
    suspend fun autoTagAllBirthdays(daysRange: Int = 3): Int = withContext(Dispatchers.IO) {
        val persons = personDao.getAllPersons()
        var totalTags = 0

        persons.forEach { person ->
            if (person.dateOfBirth != null) {
                totalTags += autoTagBirthdaysForPerson(person.id, daysRange)
            }
        }

        DiagnosticLogger.i("AutoTag: Applied $totalTags birthday tags")
        totalTags
    }

    // ======================
    // HELPER METHODS
    // ======================

    /**
     * Check if an image is a selfie based on face size
     */
    private fun isSelfie(face: DetectedFace, bitmap: Bitmap): Boolean {
        val boundingBox = face.boundingBox
        val faceArea = boundingBox.width() * boundingBox.height()
        val imageArea = bitmap.width * bitmap.height
        val faceRatio = faceArea.toFloat() / imageArea.toFloat()

        // Selfie = face takes up significant portion (>15% of image)
        return faceRatio > 0.15f
    }

    /**
     * Get hour of day from timestamp (0-23)
     */
    private fun getHourFromTimestamp(timestamp: Long): Int {
        return Calendar.getInstance().apply {
            timeInMillis = timestamp
        }.get(Calendar.HOUR_OF_DAY)
    }

    /**
     * Check if a timestamp is near a birthday
     */
    private fun isNearBirthday(
        capturedTimestamp: Long,
        dobTimestamp: Long,
        daysRange: Int
    ): Boolean {
        val capturedCal = Calendar.getInstance().apply { timeInMillis = capturedTimestamp }
        val dobCal = Calendar.getInstance().apply { timeInMillis = dobTimestamp }

        val capturedMonth = capturedCal.get(Calendar.MONTH)
        val capturedDay = capturedCal.get(Calendar.DAY_OF_MONTH)
        val dobMonth = dobCal.get(Calendar.MONTH)
        val dobDay = dobCal.get(Calendar.DAY_OF_MONTH)

        if (capturedMonth == dobMonth) {
            return abs(capturedDay - dobDay) <= daysRange
        }

        // Handle edge case: birthday near end/start of month
        // e.g., DOB = Jan 2, captured = Dec 31 (within 3 days)
        if (abs(capturedMonth - dobMonth) == 1 || abs(capturedMonth - dobMonth) == 11) {
            val daysInCapturedMonth = capturedCal.getActualMaximum(Calendar.DAY_OF_MONTH)
            val daysInDobMonth = dobCal.getActualMaximum(Calendar.DAY_OF_MONTH)

            if (capturedMonth < dobMonth || (capturedMonth == 11 && dobMonth == 0)) {
                // Captured before DOB month
                val dayDiff = (daysInCapturedMonth - capturedDay) + dobDay
                return dayDiff <= daysRange
            } else {
                // Captured after DOB month
                val dayDiff = (daysInDobMonth - dobDay) + capturedDay
                return dayDiff <= daysRange
            }
        }

        return false
    }

    /**
     * Basic indoor/outdoor estimation using brightness and saturation
     *
     * Heuristic:
     * - Outdoor: Higher brightness (>120), Higher saturation (>0.25)
     * - Indoor: Lower brightness, Lower saturation
     */
    private fun estimateIndoorOutdoor(bitmap: Bitmap): Boolean {
        // Sample pixels for analysis (don't process entire image)
        val sampleSize = 100
        val sampledPixels = mutableListOf<Int>()

        val stepX = bitmap.width / sampleSize.coerceAtMost(bitmap.width)
        val stepY = bitmap.height / sampleSize.coerceAtMost(bitmap.height)

        for (x in 0 until sampleSize.coerceAtMost(bitmap.width)) {
            for (y in 0 until sampleSize.coerceAtMost(bitmap.height)) {
                val px = (x * stepX).coerceIn(0, bitmap.width - 1)
                val py = (y * stepY).coerceIn(0, bitmap.height - 1)
                sampledPixels.add(bitmap.getPixel(px, py))
            }
        }

        if (sampledPixels.isEmpty()) return true // Default to indoor if sampling fails

        // Calculate average brightness
        val avgBrightness = sampledPixels.map { pixel ->
            val r = Color.red(pixel)
            val g = Color.green(pixel)
            val b = Color.blue(pixel)
            (r + g + b) / 3.0f
        }.average()

        // Calculate color saturation
        val avgSaturation = sampledPixels.map { pixel ->
            val hsv = FloatArray(3)
            Color.colorToHSV(pixel, hsv)
            hsv[1] // Saturation
        }.average()

        // Heuristic: Indoor if low brightness OR low saturation
        return avgBrightness < 120 || avgSaturation < 0.25
    }

    /**
     * Apply a system tag to an image (helper to avoid duplicates)
     *
     * @return true if tag was applied, false if already exists
     */
    private suspend fun applySystemTag(imageId: String, tagName: String): Boolean {
        return withContext(Dispatchers.IO) {
            try {
                // Get or create tag
                val tag = getOrCreateSystemTag(tagName)

                // Create image-tag link
                val imageTag = ImageTagEntity(
                    imageId = imageId,
                    tagId = tag.tagId,
                    source = "AUTO",
                    confidence = 1.0f,
                    visibility = "PUBLIC",
                    createdAt = System.currentTimeMillis()
                )

                imageTagDao.upsert(imageTag)
                true
            } catch (e: Exception) {
                DiagnosticLogger.e("Failed to apply tag '$tagName' to image $imageId", e)
                false
            }
        }
    }

    /**
     * Get existing system tag or create new one
     */
    private suspend fun getOrCreateSystemTag(tagName: String): TagEntity {
        return withContext(Dispatchers.IO) {
            tagDao.getByValue(tagName) ?: run {
                val newTag = TagEntity.createSystemTag(tagName)
                tagDao.insert(newTag)
                newTag
            }
        }
    }
}