package de.krisenvorrat.app.data.export

import de.krisenvorrat.app.data.db.dao.CategoryDao
import de.krisenvorrat.app.data.db.dao.ItemDao
import de.krisenvorrat.app.data.db.dao.LocationDao
import de.krisenvorrat.app.data.db.dao.SettingsDao
import de.krisenvorrat.app.data.db.entity.CategoryEntity
import de.krisenvorrat.app.data.db.entity.ItemEntity
import de.krisenvorrat.app.data.db.entity.LocationEntity
import de.krisenvorrat.app.data.db.entity.SettingsEntity
import de.krisenvorrat.app.domain.model.SettingsKeys
import de.krisenvorrat.app.domain.model.parseAgeGroupsFromJson
import de.krisenvorrat.app.domain.model.totalDailyKcal
import de.krisenvorrat.app.domain.repository.ImportExportRepository
import de.krisenvorrat.shared.model.CategoryDto
import de.krisenvorrat.shared.model.InventoryDto
import de.krisenvorrat.shared.model.ItemDto
import de.krisenvorrat.shared.model.LocationDto
import de.krisenvorrat.shared.model.SettingDto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.Locale
import javax.inject.Inject

internal class ImportExportRepositoryImpl @Inject constructor(
    private val categoryDao: CategoryDao,
    private val locationDao: LocationDao,
    private val itemDao: ItemDao,
    private val settingsDao: SettingsDao,
    private val transaction: DatabaseTransaction
) : ImportExportRepository {

    private val jsonSerializer = Json {
        prettyPrint = false
        ignoreUnknownKeys = true
    }

    override suspend fun exportToJson(): String = withContext(Dispatchers.IO) {
        val exportData = buildInventoryDto()
        jsonSerializer.encodeToString(InventoryDto.serializer(), exportData)
    }

    override suspend fun exportToInventoryDto(): InventoryDto = withContext(Dispatchers.IO) {
        buildInventoryDto()
    }

    private suspend fun buildInventoryDto(): InventoryDto {
        val categories = categoryDao.getAll().first()
        val locations = locationDao.getAll().first()
        val items = itemDao.getAll().first()
        val settings = settingsDao.getAll().first()
            .filter { it.key !in SettingsKeys.SENSITIVE_KEYS }

        return InventoryDto(
            categories = categories.map { CategoryDto(id = it.id, name = it.name) },
            locations = locations.map { LocationDto(id = it.id, name = it.name) },
            items = items.map { item ->
                ItemDto(
                    id = item.id,
                    name = item.name,
                    categoryId = item.categoryId,
                    quantity = item.quantity,
                    unit = item.unit,
                    unitPrice = item.unitPrice,
                    kcalPerKg = item.kcalPerKg,
                    expiryDate = item.expiryDate?.toString(),
                    locationId = item.locationId,
                    notes = item.notes,
                    lastUpdated = item.lastUpdated
                )
            },
            settings = settings.map { SettingDto(key = it.key, value = it.value) }
        )
    }

    override suspend fun importFromJson(json: String): Result<Unit> = withContext(Dispatchers.IO) {
        runCatching {
            val exportData = jsonSerializer.decodeFromString<InventoryDto>(json)
            check(exportData.version == 1) { "Unsupported export format version: ${exportData.version}" }
            applyInventoryDto(exportData)
        }
    }

    override suspend fun importFromInventoryDto(dto: InventoryDto): Result<Unit> = withContext(Dispatchers.IO) {
        runCatching {
            applyInventoryDto(dto)
        }
    }

    private suspend fun applyInventoryDto(dto: InventoryDto) {
        val localItems = itemDao.getAll().first()
        val localMap = localItems.associateBy { it.id }
        val itemsToApply = dto.items.filter { item ->
            item.lastUpdated > (localMap[item.id]?.lastUpdated ?: -1L)
        }
        transaction.execute {
            categoryDao.upsertAll(dto.categories.map { CategoryEntity(id = it.id, name = it.name) })
            locationDao.upsertAll(dto.locations.map { LocationEntity(id = it.id, name = it.name) })
            itemDao.upsertAll(itemsToApply.map { item ->
                ItemEntity(
                    id = item.id,
                    name = item.name,
                    categoryId = item.categoryId,
                    quantity = item.quantity,
                    unit = item.unit,
                    unitPrice = item.unitPrice,
                    kcalPerKg = item.kcalPerKg,
                    expiryDate = item.expiryDate?.let { LocalDate.parse(it) },
                    locationId = item.locationId,
                    notes = item.notes,
                    lastUpdated = item.lastUpdated
                )
            })
            settingsDao.upsertAll(
                dto.settings
                    .filter { it.key !in SettingsKeys.SENSITIVE_KEYS }
                    .map { SettingsEntity(key = it.key, value = it.value) }
            )
        }
    }

    override suspend fun exportToMarkdown(): String = withContext(Dispatchers.IO) {
        val categories = categoryDao.getAll().first()
        val locations = locationDao.getAll().first()
        val items = itemDao.getAll().first()
        val settings = settingsDao.getAll().first()

        val locationMap = locations.associate { it.id to it.name }
        val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale.GERMAN)

        val sb = StringBuilder()
        sb.appendLine("# Krisenvorrat Inventar")
        sb.appendLine()

        for (category in categories) {
            val categoryItems = items.filter { it.categoryId == category.id }
            if (categoryItems.isEmpty()) continue

            sb.appendLine("## ${category.name}")
            sb.appendLine()
            sb.appendLine("| Name | Menge | Einheit | MHD | Lagerort |")
            sb.appendLine("|------|-------|---------|-----|----------|")
            for (item in categoryItems) {
                val quantity = formatQuantity(item.quantity)
                val expiryDate = item.expiryDate?.format(dateFormatter) ?: "–"
                val location = locationMap[item.locationId] ?: "–"
                sb.appendLine("| ${item.name} | $quantity | ${item.unit} | $expiryDate | $location |")
            }
            sb.appendLine()
        }

        val settingsMap = settings.associate { it.key to it.value }
        val ageGroupsJson = settingsMap["age_groups"]
        if (ageGroupsJson != null) {
            val totalDailyKcal = parseAgeGroupsFromJson(ageGroupsJson).totalDailyKcal()
            if (totalDailyKcal > 0) {
                sb.appendLine("## Einstellungen")
                sb.appendLine()
                sb.appendLine("- **Haushalt kcal/Tag:** $totalDailyKcal")
                sb.appendLine()
            }
        }

        sb.toString().trimEnd() + "\n"
    }

    private fun formatQuantity(value: Double): String {
        return if (value == value.toLong().toDouble()) {
            value.toLong().toString()
        } else {
            String.format(Locale.GERMAN, "%.1f", value)
        }
    }
}
