package com.ffii.fpsms.modules.master.service

import com.ffii.core.exception.NotFoundException
import com.ffii.fpsms.modules.master.entity.*
import com.ffii.fpsms.modules.master.web.models.*
import com.ffii.fpsms.modules.qc.entity.projection.QcItemInfo
import jakarta.validation.Valid
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.RequestBody
import com.ffii.core.exception.ConflictException
@Service
open class QcItemAllService(
    private val qcCategoryRepository: QcCategoryRepository,
    private val qcItemRepository: QcItemRepository,
    private val qcItemCategoryRepository: QcItemCategoryRepository,
    private val itemsQcCategoryMappingRepository: ItemsQcCategoryMappingRepository,
    private val itemsRepository: ItemsRepository
) {
    
    // Get item count by qc category
    open fun getItemCountByQcCategory(qcCategoryId: Long): Long {
        return itemsQcCategoryMappingRepository.countByQcCategoryId(qcCategoryId)
    }
    
    // Get qc item count by qc category
    open fun getQcItemCountByQcCategory(qcCategoryId: Long): Long {
        return qcItemCategoryRepository.findAllByQcCategoryId(qcCategoryId).size.toLong()
    }
    
    // Get all qc categories with item counts (batch operation for performance)
    open fun getAllQcCategoriesWithItemCounts(): List<QcCategoryWithItemCount> {
        val categories = qcCategoryRepository.findAllByDeletedIsFalse()
        val allMappings = itemsQcCategoryMappingRepository.findAll()
        
        // Group mappings by qcCategoryId and count
        val countMap = allMappings
            .filter { it.qcCategoryId != null }
            .groupBy { it.qcCategoryId!! }
            .mapValues { it.value.size.toLong() }
        
        return categories.map { category ->
            QcCategoryWithItemCount(
                id = category.id!!,
                code = category.code,
                name = category.name,
                description = category.description,
                itemCount = countMap[category.id] ?: 0L
            )
        }
    }
        // Get all qc categories with item counts and type (type from first mapping if any)
        open fun getAllQcCategoriesWithItemCountsAndType(): List<QcCategoryWithItemCountAndType> {
            val categories = qcCategoryRepository.findAllByDeletedIsFalse()
            val allMappings = itemsQcCategoryMappingRepository.findAll()
            val countMap = allMappings
                .filter { it.qcCategoryId != null }
                .groupBy { it.qcCategoryId!! }
                .mapValues { it.value.size.toLong() }
                val typeMap = allMappings
                .filter { it.qcCategoryId != null && it.type != null }
                .groupBy { it.qcCategoryId!! }
                .mapValues { it.value.first().type }
            return categories.map { category ->
                val rawType = typeMap[category.id]
                val normalizedType = if (rawType == "[object Object]") null else rawType
                QcCategoryWithItemCountAndType(
                    id = category.id!!,
                    code = category.code,
                    name = category.name,
                    description = category.description,
                    itemCount = countMap[category.id] ?: 0L,
                    type = normalizedType
                )
            }
        }
    
        // Get type for one category (from any mapping; null if no mappings). For 映射詳情 dropdown default.
        open fun getCategoryType(qcCategoryId: Long): String? {
            val list = itemsQcCategoryMappingRepository.findAllByQcCategoryId(qcCategoryId)
            val rawType = list.firstOrNull()?.type
            return if (rawType == "[object Object]") null else rawType
        }
    
        // Batch update type for all mappings of a category. 方案 A: no mappings -> no-op.
        open fun updateCategoryType(qcCategoryId: Long, type: String) {
            val mappings = itemsQcCategoryMappingRepository.findAllByQcCategoryId(qcCategoryId)
            if (mappings.isEmpty()) return   // 方案 A：沒有映射就直接結束
        
            // 先取得目前這個 Category，用來組錯誤訊息
            val currentCategory = qcCategoryRepository.findById(qcCategoryId).orElse(null)
            val currentCategoryName = currentCategory?.name ?: currentCategory?.code ?: qcCategoryId.toString()
        
            // 檢查：這些 itemId + 新 type 在別的 category 有沒有出現
            mappings.forEach { mapping ->
                val itemId = mapping.itemId ?: return@forEach
        
                // 找出「同一個 item + type」的其他映射
                val conflict = itemsQcCategoryMappingRepository.findByItemIdAndType(itemId, type)
                if (conflict != null && conflict.qcCategoryId != qcCategoryId) {
                    // 取出物料與衝突的 category 名稱
                    val item = itemsRepository.findById(itemId).orElse(null)
                    val itemCode = item?.code ?: itemId.toString()
        
                    val conflictCategoryId = conflict.qcCategoryId
                    val conflictCategory = conflictCategoryId?.let { qcCategoryRepository.findById(it).orElse(null) }
                    val conflictCategoryName =
                        conflictCategory?.name ?: conflictCategory?.code ?: conflictCategoryId.toString()
        
                    // 用 ConflictException 讓 ErrorHandler 回 409 + { "error": msg }
                    val msg = "物料 $itemCode 已經以 $type 映射在模板 $conflictCategoryName，" +
                              "不能再把模板 $currentCategoryName 改為 $type"
                    throw ConflictException(msg)
                }
            }
        
            // 檢查通過才真正更新
            mappings.forEach { it.type = type }
            itemsQcCategoryMappingRepository.saveAll(mappings)
        }
    
    // Get all qc categories with qc item counts (batch operation for performance)
    open fun getAllQcCategoriesWithQcItemCounts(): List<QcCategoryWithQcItemCount> {
        val categories = qcCategoryRepository.findAllByDeletedIsFalse()
        val allMappings = qcItemCategoryRepository.findAll()
        
        // Group mappings by qcCategoryId and count
        val countMap = allMappings
            .filter { it.qcCategory?.id != null }
            .groupBy { it.qcCategory!!.id!! }
            .mapValues { it.value.size.toLong() }
        
        return categories.map { category ->
            QcCategoryWithQcItemCount(
                id = category.id!!,
                code = category.code,
                name = category.name,
                description = category.description,
                qcItemCount = countMap[category.id] ?: 0L
            )
        }
    }
    
    // Check if can delete qc category
    open fun canDeleteQcCategory(id: Long): Boolean {
        val itemCount = getItemCountByQcCategory(id)
        val qcItemCount = getQcItemCountByQcCategory(id)
        return itemCount == 0L && qcItemCount == 0L
    }
    
    // Check if can delete qc item
    open fun canDeleteQcItem(id: Long): Boolean {
        // Check if this qc item is linked to any qc category
        val count = qcItemCategoryRepository.countByQcItemId(id)
        return count == 0L
    }
    
    // Get item and qc category mappings
    open fun getItemQcCategoryMappings(qcCategoryId: Long?, itemId: Long?): List<ItemQcCategoryMappingInfo> {
        val mappings = itemsQcCategoryMappingRepository.findMappings(qcCategoryId, itemId)
        return mappings.map { mapping ->
            val item = if (mapping.itemId != null) itemsRepository.findById(mapping.itemId!!).orElse(null) else null
            val qcCategory = if (mapping.qcCategoryId != null) qcCategoryRepository.findById(mapping.qcCategoryId!!).orElse(null) else null
            
            ItemQcCategoryMappingInfo(
                id = mapping.id!!,
                itemId = mapping.itemId ?: 0L,
                itemCode = item?.code,
                itemName = item?.name,
                qcCategoryId = mapping.qcCategoryId ?: 0L,
                qcCategoryCode = qcCategory?.code,
                qcCategoryName = qcCategory?.name,
                type = mapping.type
            )
        }
    }
    
    // Save item and qc category mapping
    open fun saveItemQcCategoryMapping(itemId: Long, qcCategoryId: Long, type: String): ItemQcCategoryMappingInfo {
        // Check if mapping already exists
        val existing = itemsQcCategoryMappingRepository.findByItemIdAndQcCategoryIdAndType(itemId, qcCategoryId, type)
        val existingByItem = itemsQcCategoryMappingRepository.findAllByItemId(itemId)
        val sameTypeOtherCategory = existingByItem.firstOrNull { it.type == type && it.qcCategoryId != qcCategoryId }
        if (sameTypeOtherCategory != null) {
            val category = qcCategoryRepository.findById(sameTypeOtherCategory.qcCategoryId!!).orElse(null)
            val msg = "Item already has type \"$type\" linked to QcCategory: ${category?.name ?: sameTypeOtherCategory.qcCategoryId}. One item can only have each type in one QcCategory."
            throw ConflictException(msg)
        }
        val mapping = existing ?: ItemsQcCategoryMapping()
        
        mapping.itemId = itemId
        mapping.qcCategoryId = qcCategoryId
        mapping.type = type
        
        val saved = itemsQcCategoryMappingRepository.save(mapping)
        
        val item = itemsRepository.findById(itemId).orElseThrow { NotFoundException() }
        val qcCategory = qcCategoryRepository.findById(qcCategoryId).orElseThrow { NotFoundException() }
        
        return ItemQcCategoryMappingInfo(
            id = saved.id!!,
            itemId = saved.itemId ?: 0L,
            itemCode = item.code,
            itemName = item.name,
            qcCategoryId = saved.qcCategoryId ?: 0L,
            qcCategoryCode = qcCategory.code,
            qcCategoryName = qcCategory.name,
            type = saved.type
        )
    }
    
    // Delete item and qc category mapping
    open fun deleteItemQcCategoryMapping(mappingId: Long) {
        itemsQcCategoryMappingRepository.deleteById(mappingId)
    }
    
    // Get qc category and qc item mappings
    open fun getQcCategoryQcItemMappings(qcCategoryId: Long): List<QcItemInfo> {
        return qcItemCategoryRepository.findAllByQcCategoryId(qcCategoryId)
    }
    
    // Save qc category and qc item mapping
    open fun saveQcCategoryQcItemMapping(
        qcCategoryId: Long,
        qcItemId: Long,
        order: Int,
        description: String?
    ): QcItemCategory {
        val qcCategory = qcCategoryRepository.findById(qcCategoryId).orElseThrow { NotFoundException() }
        val qcItem = qcItemRepository.findById(qcItemId).orElseThrow { NotFoundException() }
        
        // Check if mapping already exists
        val existing = qcCategory.qcItemCategory.firstOrNull { it.qcItem?.id == qcItemId }
        val mapping = existing ?: QcItemCategory()
        
        mapping.qcCategory = qcCategory
        mapping.qcItem = qcItem
        mapping.order = order
        // Description is optional - use qcItem's description if not provided
        mapping.description = description ?: qcItem.description
        
        if (existing == null) {
            qcCategory.qcItemCategory.add(mapping)
        }
        
        val savedCategory = qcCategoryRepository.save(qcCategory)
        
        // Return the saved mapping from the saved category to avoid lazy loading issues
        return savedCategory.qcItemCategory.firstOrNull { it.qcItem?.id == qcItemId } ?: mapping
    }
    
    // Delete qc category and qc item mapping
    open fun deleteQcCategoryQcItemMapping(mappingId: Long) {
        val mapping = qcItemCategoryRepository.findById(mappingId).orElseThrow { NotFoundException() }
        val qcCategory = mapping.qcCategory
        if (qcCategory != null) {
            qcCategory.qcItemCategory.remove(mapping)
            qcCategoryRepository.save(qcCategory)
        }
        qcItemCategoryRepository.delete(mapping)
    }
    
    // Save qc category with validation
    open fun saveQcCategoryWithValidation(@Valid @RequestBody request: SaveQcCategoryRequest): SaveQcCategoryResponse {
        val errors = mutableMapOf<String, String>()
        val id = request.id
        val qcCategory = if (id != null) qcCategoryRepository.findById(id).orElseThrow() else QcCategory()
        
        if (errors.isNotEmpty()) {
            return SaveQcCategoryResponse(
                id = request.id,
                code = request.code,
                name = request.name,
                description = request.description,
                errors = errors
            )
        }
        
        qcCategory.apply {
            code = request.code
            name = request.name
            description = request.description
        }
        
        val savedQcCategory = qcCategoryRepository.save(qcCategory)
        
        return SaveQcCategoryResponse(
            id = savedQcCategory.id,
            code = savedQcCategory.code,
            name = savedQcCategory.name,
            description = savedQcCategory.description,
            errors = null
        )
    }
    
    // Delete qc category with validation
    open fun deleteQcCategoryWithValidation(id: Long): DeleteResponse {
        if (!canDeleteQcCategory(id)) {
            val itemCount = getItemCountByQcCategory(id)
            val qcItemCount = getQcItemCountByQcCategory(id)
            return DeleteResponse(
                success = false,
                canDelete = false,
                message = "Cannot delete QcCategory. It has $itemCount item(s) and $qcItemCount qc item(s) linked to it."
            )
        }
        
        val qcCategory = qcCategoryRepository.findById(id).orElseThrow { NotFoundException() }
        qcCategory.deleted = true
        qcCategoryRepository.save(qcCategory)
        
        return DeleteResponse(
            success = true,
            canDelete = true,
            message = "QcCategory deleted successfully"
        )
    }
    
    // Save qc item with validation
    open fun saveQcItemWithValidation(@Valid @RequestBody request: SaveQcItemRequest): SaveQcItemResponse {
        val errors = mutableMapOf<String, String>()
        val id = request.id
        val qcItem = if (id != null) qcItemRepository.findById(id).orElseThrow() else QcItem()
        
        if (errors.isNotEmpty()) {
            return SaveQcItemResponse(
                id = request.id,
                code = request.code,
                name = request.name,
                description = request.description,
                errors = errors
            )
        }
        
        qcItem.apply {
            code = request.code
            name = request.name
            description = request.description
        }
        
        val savedQcItem = qcItemRepository.save(qcItem)
        
        return SaveQcItemResponse(
            id = savedQcItem.id,
            code = savedQcItem.code,
            name = savedQcItem.name,
            description = savedQcItem.description,
            errors = null
        )
    }
    
    // Delete qc item with validation
    open fun deleteQcItemWithValidation(id: Long): DeleteResponse {
        if (!canDeleteQcItem(id)) {
            return DeleteResponse(
                success = false,
                canDelete = false,
                message = "Cannot delete QcItem. It is linked to one or more QcCategories."
            )
        }
        
        val qcItem = qcItemRepository.findById(id).orElseThrow { NotFoundException() }
        qcItem.deleted = true
        qcItemRepository.save(qcItem)
        
        return DeleteResponse(
            success = true,
            canDelete = true,
            message = "QcItem deleted successfully"
        )
    }
    
    // Get item by code (for Tab 0 - validate item code input)
    open fun getItemByCode(code: String): Items? {
        return itemsRepository.findByCodeAndDeletedFalse(code)
    }
}

