package app.closer.domain

import app.closer.domain.model.MemoryCapsule
import app.closer.domain.model.MemoryCapsuleSource
import app.closer.domain.model.MemoryChallengeEvent
import app.closer.domain.model.MemoryDateIdeaEvent
import app.closer.domain.model.MemoryManualInput
import app.closer.domain.model.MemoryRecapEvent
import app.closer.domain.model.MemoryRevealEvent
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit

/**
 * Pure, deterministic logic for creating [MemoryCapsule] instances from couple events.
 *
 * No Android dependencies. The generator receives neutral, privacy-safe event metadata
 * and produces a capsule with a title, date, category, optional encrypted note, optional
 * photo path, and auto-generated tags.
 *
 * Privacy rules enforced here:
 * - Decrypted answer text is never read or included in generated output.
 * - Reveal-derived titles use only category metadata and the fact that a reveal happened.
 * - Manual notes are accepted as already-encrypted ciphertext; this object does not encrypt.
 * - Tags are built from category, source type, and date context only.
 */
object MemoryCapsuleGenerator {

    /**
     * Default category for a memory when no specific category is supplied.
     */
    internal const val DEFAULT_CATEGORY = "memory"

    /**
     * Category key for a saved date idea that becomes a memory.
     */
    internal const val DATE_CATEGORY = "date"

    /**
     * Category key for a reveal that becomes a memory.
     */
    internal const val REVEAL_CATEGORY = "reveal"

    /**
     * Category key for a weekly recap that becomes a memory.
     */
    internal const val RECAP_CATEGORY = "recap"

    /**
     * Category key for a completed challenge that becomes a memory.
     */
    internal const val CHALLENGE_CATEGORY = "challenge"

    /**
     * Creates a memory capsule from a saved date idea after the date has happened.
     *
     * @param coupleId couple that owns the memory
     * @param authorId user creating the memory
     * @param event the saved date idea event
     * @param noteCiphertext optional encrypted post-date note
     * @param photoStoragePath optional photo path
     * @param now current date used when [event.scheduledDate] is absent
     */
    fun fromDateIdea(
        coupleId: String,
        authorId: String,
        event: MemoryDateIdeaEvent,
        noteCiphertext: String? = null,
        photoStoragePath: String? = null,
        now: LocalDate = LocalDate.now()
    ): MemoryCapsule {
        val date = event.scheduledDate ?: now
        val displayCategory = event.category.takeIf { it.isNotBlank() } ?: DATE_CATEGORY
        val title = when {
            event.title.isNotBlank() -> "Our ${displayTitleForCategory(displayCategory)} date: ${event.title}"
            else -> "Our ${displayTitleForCategory(displayCategory)} date"
        }

        return MemoryCapsule(
            id = "",
            coupleId = coupleId,
            authorId = authorId,
            title = title,
            date = date,
            category = displayCategory,
            noteCiphertext = noteCiphertext,
            photoStoragePath = photoStoragePath,
            tags = buildTags(
                category = displayCategory,
                source = MemoryCapsuleSource.DATE_IDEA,
                date = date
            ),
            source = MemoryCapsuleSource.DATE_IDEA,
            sourceRefId = event.dateIdeaId,
            createdAt = System.currentTimeMillis()
        )
    }

    /**
     * Creates a memory capsule from a reveal when both partners agree to save it.
     *
     * The generated title never contains decrypted answer text.
     */
    fun fromReveal(
        coupleId: String,
        authorId: String,
        event: MemoryRevealEvent,
        noteCiphertext: String? = null,
        photoStoragePath: String? = null
    ): MemoryCapsule {
        val displayCategory = event.categoryId?.takeIf { it.isNotBlank() } ?: REVEAL_CATEGORY
        val title = "A moment we revealed together${categoryFragment(displayCategory)}"

        return MemoryCapsule(
            id = "",
            coupleId = coupleId,
            authorId = authorId,
            title = title,
            date = event.revealedDate,
            category = displayCategory,
            noteCiphertext = noteCiphertext,
            photoStoragePath = photoStoragePath,
            tags = buildTags(
                category = displayCategory,
                source = MemoryCapsuleSource.REVEAL,
                date = event.revealedDate
            ),
            source = MemoryCapsuleSource.REVEAL,
            sourceRefId = event.questionId,
            createdAt = System.currentTimeMillis()
        )
    }

    /**
     * Creates a memory capsule from a completed weekly recap.
     */
    fun fromWeeklyRecap(
        coupleId: String,
        authorId: String,
        event: MemoryRecapEvent,
        noteCiphertext: String? = null,
        photoStoragePath: String? = null
    ): MemoryCapsule {
        val displayCategory = event.favoriteCategory?.takeIf { it.isNotBlank() } ?: RECAP_CATEGORY
        val weekNumber = weekNumberLabel(event.weekStart, event.weekEnd)
        val title = "Week $weekNumber together${categoryFragment(displayCategory)}"

        return MemoryCapsule(
            id = "",
            coupleId = coupleId,
            authorId = authorId,
            title = title,
            date = event.weekEnd,
            category = displayCategory,
            noteCiphertext = noteCiphertext,
            photoStoragePath = photoStoragePath,
            tags = buildTags(
                category = displayCategory,
                source = MemoryCapsuleSource.WEEKLY_RECAP,
                date = event.weekEnd,
                extras = weekNumber?.let { listOf("week-$it") } ?: emptyList()
            ),
            source = MemoryCapsuleSource.WEEKLY_RECAP,
            sourceRefId = event.recapId,
            createdAt = System.currentTimeMillis()
        )
    }

    /**
     * Creates a memory capsule from a completed challenge.
     */
    fun fromChallenge(
        coupleId: String,
        authorId: String,
        event: MemoryChallengeEvent,
        noteCiphertext: String? = null,
        photoStoragePath: String? = null
    ): MemoryCapsule {
        val displayCategory = event.challengeCategory?.takeIf { it.isNotBlank() } ?: CHALLENGE_CATEGORY
        val title = "Challenge completed${categoryFragment(displayCategory)}"

        return MemoryCapsule(
            id = "",
            coupleId = coupleId,
            authorId = authorId,
            title = title,
            date = event.completedDate,
            category = displayCategory,
            noteCiphertext = noteCiphertext,
            photoStoragePath = photoStoragePath,
            tags = buildTags(
                category = displayCategory,
                source = MemoryCapsuleSource.CHALLENGE,
                date = event.completedDate
            ),
            source = MemoryCapsuleSource.CHALLENGE,
            sourceRefId = event.challengeId,
            createdAt = System.currentTimeMillis()
        )
    }

    /**
     * Creates a memory capsule from manual user input.
     *
     * The caller is responsible for encrypting [input.noteCiphertext] before invoking
     * this generator. This object never sees plaintext notes.
     */
    fun fromManualInput(
        coupleId: String,
        authorId: String,
        input: MemoryManualInput,
        now: LocalDate = LocalDate.now()
    ): MemoryCapsule {
        val displayCategory = input.category.takeIf { it.isNotBlank() } ?: DEFAULT_CATEGORY
        val title = input.title.takeIf { it.isNotBlank() }
            ?: "A moment from ${input.date.format(DateTimeFormatter.ofPattern("MMMM d"))}"

        return MemoryCapsule(
            id = "",
            coupleId = coupleId,
            authorId = authorId,
            title = title,
            date = input.date,
            category = displayCategory,
            noteCiphertext = input.noteCiphertext,
            photoStoragePath = input.photoStoragePath,
            tags = buildTags(
                category = displayCategory,
                source = MemoryCapsuleSource.MANUAL,
                date = input.date,
                now = now
            ),
            source = MemoryCapsuleSource.MANUAL,
            createdAt = System.currentTimeMillis()
        )
    }

    /**
     * Builds tags from safe context metadata.
     *
     * Tags are lowercase, hyphenated, and never contain user text.
     */
    internal fun buildTags(
        category: String,
        source: MemoryCapsuleSource,
        date: LocalDate,
        extras: List<String> = emptyList(),
        now: LocalDate = LocalDate.now()
    ): List<String> {
        val tags = mutableListOf<String>()

        val normalizedCategory = category.lowercase().replace(" ", "-")
        if (normalizedCategory.isNotBlank()) {
            tags.add(normalizedCategory)
        }

        tags.add(source.toFirestoreValue().replace("_", "-"))

        val monthTag = date.month.name.lowercase()
        tags.add(monthTag)

        val seasonTag = seasonForMonth(date.monthValue)
        tags.add(seasonTag)

        if (date.year == now.year) {
            tags.add("this-year")
        }

        val dayTag = date.dayOfWeek.name.lowercase()
        tags.add(dayTag)

        extras.forEach { extra ->
            val normalized = extra.lowercase().replace(" ", "-")
            if (normalized.isNotBlank() && normalized !in tags) {
                tags.add(normalized)
            }
        }

        return tags.distinct()
    }

    /**
     * Returns a human-readable category fragment for titles, e.g. " in Intimacy".
     *
     * Falls back to empty string for generic categories so titles stay warm and short.
     */
    internal fun categoryFragment(category: String): String {
        if (category.isBlank()) return ""
        val normalized = category.lowercase()
        if (normalized in setOf("date", "reveal", "recap", "challenge", "memory")) return ""
        val display = displayTitleForCategory(normalized)
        return " in $display"
    }

    /**
     * Built-in display names for known category keys.
     */
    private val CATEGORY_DISPLAY_NAMES: Map<String, String> = mapOf(
        "adventure" to "Adventure",
        "travel" to "Travel",
        "food" to "Food",
        "learning" to "Learning",
        "romance" to "Romance",
        "intimacy" to "Intimacy",
        "connection" to "Connection",
        "seasonal" to "Seasonal",
        "play" to "Play",
        "date" to "Date",
        "reveal" to "Reveal",
        "recap" to "Recap",
        "challenge" to "Challenge",
        "memory" to "Memory"
    )

    /**
     * Returns a friendly display word for a category in a title.
     */
    internal fun displayTitleForCategory(category: String): String {
        val normalized = category.lowercase()
        return CATEGORY_DISPLAY_NAMES[normalized]
            ?: normalized.replaceFirstChar { it.uppercase() }
    }

    /**
     * Returns a week number label from a recap's week start/end.
     *
     * Weeks are numbered from the start of the year. Returns null when inputs are missing.
     */
    internal fun weekNumberLabel(weekStart: LocalDate?, weekEnd: LocalDate?): String? {
        if (weekStart == null || weekEnd == null) return null
        val isoWeek = weekEnd.get(java.time.temporal.WeekFields.ISO.weekOfWeekBasedYear())
        return isoWeek.toString()
    }

    /**
     * Returns a season tag for a month number (1-12).
     */
    internal fun seasonForMonth(month: Int): String = when (month) {
        12, 1, 2 -> "winter"
        3, 4, 5 -> "spring"
        6, 7, 8 -> "summer"
        9, 10, 11 -> "fall"
        else -> "unknown"
    }
}
