package com.placeholder.sherpai2.ui.search

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.placeholder.sherpai2.data.local.dao.ImageAggregateDao
import com.placeholder.sherpai2.data.local.dao.PersonDao
import com.placeholder.sherpai2.data.local.dao.TagDao
import com.placeholder.sherpai2.data.local.entity.ImageEntity
import com.placeholder.sherpai2.data.local.entity.PersonEntity
import com.placeholder.sherpai2.data.local.entity.PhotoFaceTagEntity
import com.placeholder.sherpai2.data.repository.FaceRecognitionRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.util.Calendar
import javax.inject.Inject

@OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel
class SearchViewModel @Inject constructor(
    private val imageAggregateDao: ImageAggregateDao,
    private val faceRecognitionRepository: FaceRecognitionRepository,
    private val personDao: PersonDao,
    private val tagDao: TagDao
) : ViewModel() {

    private val _searchQuery = MutableStateFlow("")
    val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()

    private val _includedPeople = MutableStateFlow<Set<String>>(emptySet())
    val includedPeople: StateFlow<Set<String>> = _includedPeople.asStateFlow()

    private val _excludedPeople = MutableStateFlow<Set<String>>(emptySet())
    val excludedPeople: StateFlow<Set<String>> = _excludedPeople.asStateFlow()

    private val _includedTags = MutableStateFlow<Set<String>>(emptySet())
    val includedTags: StateFlow<Set<String>> = _includedTags.asStateFlow()

    private val _excludedTags = MutableStateFlow<Set<String>>(emptySet())
    val excludedTags: StateFlow<Set<String>> = _excludedTags.asStateFlow()

    private val _dateRange = MutableStateFlow(DateRange.ALL_TIME)
    val dateRange: StateFlow<DateRange> = _dateRange.asStateFlow()

    private val _faceFilter = MutableStateFlow(FaceFilter.ALL)
    val faceFilter: StateFlow<FaceFilter> = _faceFilter.asStateFlow()

    private val _availablePeople = MutableStateFlow<List<PersonEntity>>(emptyList())
    val availablePeople: StateFlow<List<PersonEntity>> = _availablePeople.asStateFlow()

    private val _availableTags = MutableStateFlow<List<String>>(emptyList())
    val availableTags: StateFlow<List<String>> = _availableTags.asStateFlow()

    private val personCache = mutableMapOf<String, String>()

    init {
        loadAvailableFilters()
        buildPersonCache()
    }

    private fun buildPersonCache() {
        viewModelScope.launch {
            val people = personDao.getAllPersons()
            people.forEach { person ->
                val stats = faceRecognitionRepository.getPersonFaceStats(person.id)
                if (stats != null) {
                    personCache[stats.faceModelId] = person.id
                }
            }
        }
    }

    fun searchImages(): Flow<List<ImageWithFaceTags>> {
        return combine(
            _searchQuery,
            _includedPeople,
            _excludedPeople,
            _includedTags,
            _excludedTags,
            _dateRange,
            _faceFilter
        ) { values: Array<*> ->
            @Suppress("UNCHECKED_CAST")
            SearchCriteria(
                query = values[0] as String,
                includedPeople = values[1] as Set<String>,
                excludedPeople = values[2] as Set<String>,
                includedTags = values[3] as Set<String>,
                excludedTags = values[4] as Set<String>,
                dateRange = values[5] as DateRange,
                faceFilter = values[6] as FaceFilter
            )
        }.flatMapLatest { criteria ->
            imageAggregateDao.observeAllImagesWithEverything()
                .map { imagesList ->
                    imagesList.mapNotNull { imageWithEverything ->
                        // Apply date filter
                        if (!isInDateRange(imageWithEverything.image.capturedAt, criteria.dateRange)) {
                            return@mapNotNull null
                        }

                        // Apply face filter - ONLY when cache is explicitly set
                        when (criteria.faceFilter) {
                            FaceFilter.HAS_FACES -> {
                                // Only show images where hasFaces is EXPLICITLY true
                                if (imageWithEverything.image.hasFaces != true) {
                                    return@mapNotNull null
                                }
                            }
                            FaceFilter.NO_FACES -> {
                                // Only show images where hasFaces is EXPLICITLY false
                                if (imageWithEverything.image.hasFaces != false) {
                                    return@mapNotNull null
                                }
                            }
                            FaceFilter.ALL -> {
                                // Show all images (null, true, or false)
                            }
                        }

                        val personIds = imageWithEverything.faceTags
                            .mapNotNull { faceTag -> personCache[faceTag.faceModelId] }
                            .toSet()

                        val imageTags = imageWithEverything.tags
                            .map { it.value }
                            .toSet()

                        val passesFilter = applyBooleanLogic(
                            personIds = personIds,
                            imageTags = imageTags,
                            criteria = criteria
                        )

                        if (passesFilter) {
                            val persons = personIds.mapNotNull { personId ->
                                _availablePeople.value.find { it.id == personId }
                            }

                            ImageWithFaceTags(
                                image = imageWithEverything.image,
                                faceTags = imageWithEverything.faceTags,
                                persons = persons
                            )
                        } else {
                            null
                        }
                    }.sortedByDescending { it.image.capturedAt }
                }
        }
    }

    private fun applyBooleanLogic(
        personIds: Set<String>,
        imageTags: Set<String>,
        criteria: SearchCriteria
    ): Boolean {
        val hasAllIncludedPeople = if (criteria.includedPeople.isNotEmpty()) {
            criteria.includedPeople.all { it in personIds }
        } else true

        val hasNoExcludedPeople = if (criteria.excludedPeople.isNotEmpty()) {
            criteria.excludedPeople.none { it in personIds }
        } else true

        val hasAllIncludedTags = if (criteria.includedTags.isNotEmpty()) {
            criteria.includedTags.all { it in imageTags }
        } else true

        val hasNoExcludedTags = if (criteria.excludedTags.isNotEmpty()) {
            criteria.excludedTags.none { it in imageTags }
        } else true

        val matchesTextSearch = if (criteria.query.isNotBlank()) {
            val normalizedQuery = criteria.query.trim().lowercase()
            imageTags.any { tag -> tag.lowercase().contains(normalizedQuery) }
        } else true

        return hasAllIncludedPeople && hasNoExcludedPeople &&
                hasAllIncludedTags && hasNoExcludedTags &&
                matchesTextSearch
    }

    private fun loadAvailableFilters() {
        viewModelScope.launch {
            val people = personDao.getAllPersons()
            _availablePeople.value = people.sortedBy { it.name }

            val tags = tagDao.getByType("SYSTEM")
            val tagsWithUsage = tags.map { tag ->
                tag to tagDao.getTagUsageCount(tag.tagId)
            }
            _availableTags.value = tagsWithUsage
                .sortedByDescending { (_, usageCount) -> usageCount }
                .take(30)
                .map { (tag, _) -> tag.value }
        }
    }

    fun includePerson(personId: String) {
        _includedPeople.value = _includedPeople.value + personId
        _excludedPeople.value = _excludedPeople.value - personId
    }

    fun excludePerson(personId: String) {
        _excludedPeople.value = _excludedPeople.value + personId
        _includedPeople.value = _includedPeople.value - personId
    }

    fun removePersonFilter(personId: String) {
        _includedPeople.value = _includedPeople.value - personId
        _excludedPeople.value = _excludedPeople.value - personId
    }

    fun includeTag(tagValue: String) {
        _includedTags.value = _includedTags.value + tagValue
        _excludedTags.value = _excludedTags.value - tagValue
    }

    fun excludeTag(tagValue: String) {
        _excludedTags.value = _excludedTags.value + tagValue
        _includedTags.value = _includedTags.value - tagValue
    }

    fun removeTagFilter(tagValue: String) {
        _includedTags.value = _includedTags.value - tagValue
        _excludedTags.value = _excludedTags.value - tagValue
    }

    fun setSearchQuery(query: String) {
        _searchQuery.value = query
    }

    fun setDateRange(range: DateRange) {
        _dateRange.value = range
    }

    fun setFaceFilter(filter: FaceFilter) {
        _faceFilter.value = filter
    }

    fun clearAllFilters() {
        _searchQuery.value = ""
        _includedPeople.value = emptySet()
        _excludedPeople.value = emptySet()
        _includedTags.value = emptySet()
        _excludedTags.value = emptySet()
        _dateRange.value = DateRange.ALL_TIME
        _faceFilter.value = FaceFilter.ALL
    }

    fun hasActiveFilters(): Boolean {
        return _searchQuery.value.isNotBlank() ||
                _includedPeople.value.isNotEmpty() ||
                _excludedPeople.value.isNotEmpty() ||
                _includedTags.value.isNotEmpty() ||
                _excludedTags.value.isNotEmpty() ||
                _dateRange.value != DateRange.ALL_TIME ||
                _faceFilter.value != FaceFilter.ALL
    }

    fun getSearchSummary(): String {
        val parts = mutableListOf<String>()
        if (_includedPeople.value.isNotEmpty()) parts.add("WITH: ${_includedPeople.value.size} people")
        if (_excludedPeople.value.isNotEmpty()) parts.add("WITHOUT: ${_excludedPeople.value.size} people")
        if (_includedTags.value.isNotEmpty()) parts.add("HAS: ${_includedTags.value.size} tags")
        if (_excludedTags.value.isNotEmpty()) parts.add("NOT: ${_excludedTags.value.size} tags")
        if (_dateRange.value != DateRange.ALL_TIME) parts.add(_dateRange.value.displayName)
        return parts.joinToString(" • ")
    }

    private fun isInDateRange(timestamp: Long, range: DateRange): Boolean = when (range) {
        DateRange.ALL_TIME -> true
        DateRange.TODAY -> isToday(timestamp)
        DateRange.THIS_WEEK -> isThisWeek(timestamp)
        DateRange.THIS_MONTH -> isThisMonth(timestamp)
        DateRange.THIS_YEAR -> isThisYear(timestamp)
    }

    private fun isToday(timestamp: Long): Boolean {
        val today = Calendar.getInstance()
        val date = Calendar.getInstance().apply { timeInMillis = timestamp }
        return today.get(Calendar.YEAR) == date.get(Calendar.YEAR) &&
                today.get(Calendar.DAY_OF_YEAR) == date.get(Calendar.DAY_OF_YEAR)
    }

    private fun isThisWeek(timestamp: Long): Boolean {
        val today = Calendar.getInstance()
        val date = Calendar.getInstance().apply { timeInMillis = timestamp }
        return today.get(Calendar.YEAR) == date.get(Calendar.YEAR) &&
                today.get(Calendar.WEEK_OF_YEAR) == date.get(Calendar.WEEK_OF_YEAR)
    }

    private fun isThisMonth(timestamp: Long): Boolean {
        val today = Calendar.getInstance()
        val date = Calendar.getInstance().apply { timeInMillis = timestamp }
        return today.get(Calendar.YEAR) == date.get(Calendar.YEAR) &&
                today.get(Calendar.MONTH) == date.get(Calendar.MONTH)
    }

    private fun isThisYear(timestamp: Long): Boolean {
        val today = Calendar.getInstance()
        val date = Calendar.getInstance().apply { timeInMillis = timestamp }
        return today.get(Calendar.YEAR) == date.get(Calendar.YEAR)
    }
}

private data class SearchCriteria(
    val query: String,
    val includedPeople: Set<String>,
    val excludedPeople: Set<String>,
    val includedTags: Set<String>,
    val excludedTags: Set<String>,
    val dateRange: DateRange,
    val faceFilter: FaceFilter
)

data class ImageWithFaceTags(
    val image: ImageEntity,
    val faceTags: List<PhotoFaceTagEntity>,
    val persons: List<PersonEntity>
)

enum class DateRange(val displayName: String) {
    ALL_TIME("All Time"),
    TODAY("Today"),
    THIS_WEEK("This Week"),
    THIS_MONTH("This Month"),
    THIS_YEAR("This Year")
}

enum class FaceFilter(val displayName: String) {
    ALL("All Photos"),
    HAS_FACES("Has Faces"),
    NO_FACES("No Faces")
}

@Deprecated("No longer used")
enum class DisplayMode { SIMPLE, VERBOSE }