package com.atridad.ascently

import org.junit.Assert.*
import org.junit.Test
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.concurrent.TimeUnit

class UtilityTests {

    @Test
    fun testDateTimeUtilities() {
        val now = System.currentTimeMillis()
        val dateTime = LocalDateTime.now()

        assertTrue(now > 0)
        assertNotNull(dateTime)

        val formatted = dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        assertFalse(formatted.isEmpty())
        assertTrue(formatted.contains("T"))
    }

    @Test
    fun testDurationCalculations() {
        val startTime = 1000L
        val endTime = 4000L
        val duration = endTime - startTime

        assertEquals(3000L, duration)

        val minutes = TimeUnit.MILLISECONDS.toMinutes(duration)
        val seconds = TimeUnit.MILLISECONDS.toSeconds(duration)

        assertEquals(0L, minutes)
        assertEquals(3L, seconds)
    }

    @Test
    fun testStringValidation() {
        val validName = "Test Gym"
        val emptyName = ""
        val whitespaceName = "   "
        val nullName: String? = null

        assertTrue(isValidString(validName))
        assertFalse(isValidString(emptyName))
        assertFalse(isValidString(whitespaceName))
        assertFalse(isValidString(nullName))
    }

    @Test
    fun testGradeConversion() {
        val vGrade = "V5"
        val ydsGrade = "5.10a"
        val fontGrade = "6A"

        assertTrue(isValidVGrade(vGrade))
        assertTrue(isValidYDSGrade(ydsGrade))
        assertTrue(isValidFontGrade(fontGrade))

        assertFalse(isValidVGrade("Invalid"))
        assertFalse(isValidYDSGrade("Invalid"))
        assertFalse(isValidFontGrade("Invalid"))
    }

    @Test
    fun testNumericGradeExtraction() {
        assertEquals(0, extractVGradeNumber("VB"))
        assertEquals(5, extractVGradeNumber("V5"))
        assertEquals(12, extractVGradeNumber("V12"))
        assertEquals(-1, extractVGradeNumber("Invalid"))
    }

    @Test
    fun testClimbingStatistics() {
        val attempts =
            listOf(
                AttemptData("SUCCESS", 120),
                AttemptData("FALL", 90),
                AttemptData("SUCCESS", 150),
                AttemptData("FLASH", 60),
                AttemptData("FALL", 110),
            )

        val stats = calculateAttemptStatistics(attempts)

        assertEquals(5, stats.totalAttempts)
        assertEquals(3, stats.successfulAttempts)
        assertEquals(60.0, stats.successRate, 0.01)
        assertEquals(106.0, stats.averageDuration, 0.01)
    }

    @Test
    fun testSessionDurationFormatting() {
        assertEquals("0m", formatDuration(0))
        assertEquals("1m", formatDuration(60))
        assertEquals("1h 30m", formatDuration(5400))
        assertEquals("2h", formatDuration(7200))
        assertEquals("2h 5m", formatDuration(7500))
    }

    @Test
    fun testDifficultyComparison() {
        assertTrue(compareVGrades("V3", "V5") < 0)
        assertTrue(compareVGrades("V5", "V3") > 0)
        assertEquals(0, compareVGrades("V5", "V5"))

        assertTrue(compareVGrades("VB", "V1") < 0)
        assertTrue(compareVGrades("V1", "VB") > 0)
    }

    @Test
    fun testClimbTypeValidation() {
        val validTypes = listOf("BOULDER", "ROPE")
        val invalidTypes = listOf("INVALID", "", "sport", "trad")

        validTypes.forEach { type -> assertTrue("$type should be valid", isValidClimbType(type)) }

        invalidTypes.forEach { type ->
            assertFalse("$type should be invalid", isValidClimbType(type))
        }
    }

    @Test
    fun testImagePathValidation() {
        val validPaths = listOf("image.jpg", "photo.jpeg", "picture.png", "diagram.webp")

        val invalidPaths = listOf("", "file.txt", "document.pdf", "video.mp4")

        validPaths.forEach { path ->
            assertTrue("$path should be valid image", isValidImagePath(path))
        }

        invalidPaths.forEach { path ->
            assertFalse("$path should be invalid image", isValidImagePath(path))
        }
    }

    @Test
    fun testLocationValidation() {
        assertTrue(isValidLocation("Wall A"))
        assertTrue(isValidLocation("Area 51"))
        assertTrue(isValidLocation("Overhang Section"))

        assertFalse(isValidLocation(""))
        assertFalse(isValidLocation("   "))
        assertFalse(isValidLocation(null))
    }

    @Test
    fun testTagProcessing() {
        val rawTags = "overhang, crimpy,  technical,DYNAMIC  "
        val processedTags = processTags(rawTags)

        assertEquals(4, processedTags.size)
        assertTrue(processedTags.contains("overhang"))
        assertTrue(processedTags.contains("crimpy"))
        assertTrue(processedTags.contains("technical"))
        assertTrue(processedTags.contains("dynamic"))
    }

    @Test
    fun testSearchFiltering() {
        val problems =
            listOf(
                ProblemData(
                    "id1",
                    "Crimpy Problem",
                    "BOULDER",
                    "V5",
                    listOf("crimpy", "overhang"),
                ),
                ProblemData("id2", "Easy Route", "ROPE", "5.6", listOf("beginner", "slab")),
                ProblemData(
                    "id3",
                    "Hard Boulder",
                    "BOULDER",
                    "V10",
                    listOf("powerful", "roof"),
                ),
            )

        val boulderProblems = filterByClimbType(problems, "BOULDER")
        assertEquals(2, boulderProblems.size)

        val crimpyProblems = filterByTag(problems, "crimpy")
        assertEquals(1, crimpyProblems.size)

        val easyProblems = filterByDifficultyRange(problems, "VB", "V6")
        assertEquals(2, easyProblems.size)
    }

    @Test
    fun testDataSynchronization() {
        val localData = mapOf("key1" to "local_value", "key2" to "shared_value")
        val serverData = mapOf("key2" to "server_value", "key3" to "new_value")

        val merged = mergeData(localData, serverData)

        assertEquals(3, merged.size)
        assertEquals("local_value", merged["key1"])
        assertEquals("server_value", merged["key2"]) // Server wins
        assertEquals("new_value", merged["key3"])
    }

    @Test
    fun testBackupValidation() {
        val validBackup =
            BackupData(
                version = "2.0",
                formatVersion = "2.0",
                exportedAt = "2024-01-01T10:00:00Z",
                dataCount = 5,
            )

        val invalidBackup =
            BackupData(
                version = "1.0",
                formatVersion = "2.0",
                exportedAt = "invalid-date",
                dataCount = -1,
            )

        assertTrue(isValidBackup(validBackup))
        assertFalse(isValidBackup(invalidBackup))
    }

    // Helper functions and data classes

    private fun isValidString(str: String?): Boolean {
        return str != null && str.trim().isNotEmpty()
    }

    private fun isValidVGrade(grade: String): Boolean {
        return grade.matches(Regex("^V(B|[0-9]|1[0-7])$"))
    }

    private fun isValidYDSGrade(grade: String): Boolean {
        return grade.matches(Regex("^5\\.[0-9]+([abcd])?$"))
    }

    private fun isValidFontGrade(grade: String): Boolean {
        return grade.matches(Regex("^[3-8][ABC]?\\+?$"))
    }

    private fun extractVGradeNumber(grade: String): Int {
        return when {
            grade == "VB" -> 0
            grade.startsWith("V") -> grade.substring(1).toIntOrNull() ?: -1
            else -> -1
        }
    }

    private fun calculateAttemptStatistics(attempts: List<AttemptData>): AttemptStatistics {
        val successful = attempts.count { it.result == "SUCCESS" || it.result == "FLASH" }
        val avgDuration = attempts.map { it.duration }.average()
        val successRate = (successful.toDouble() / attempts.size) * 100

        return AttemptStatistics(
            totalAttempts = attempts.size,
            successfulAttempts = successful,
            successRate = successRate,
            averageDuration = avgDuration,
        )
    }

    private fun formatDuration(seconds: Long): String {
        val hours = seconds / 3600
        val minutes = (seconds % 3600) / 60

        return when {
            hours > 0 && minutes > 0 -> "${hours}h ${minutes}m"
            hours > 0 -> "${hours}h"
            minutes > 0 -> "${minutes}m"
            else -> "0m"
        }
    }

    private fun compareVGrades(grade1: String, grade2: String): Int {
        val num1 = extractVGradeNumber(grade1)
        val num2 = extractVGradeNumber(grade2)
        return num1.compareTo(num2)
    }

    private fun isValidClimbType(type: String): Boolean {
        return type in listOf("BOULDER", "ROPE")
    }

    private fun isValidImagePath(path: String): Boolean {
        val validExtensions = listOf(".jpg", ".jpeg", ".png", ".webp")
        return path.isNotEmpty() && validExtensions.any { path.endsWith(it, ignoreCase = true) }
    }

    private fun isValidLocation(location: String?): Boolean {
        return isValidString(location)
    }

    private fun processTags(rawTags: String): List<String> {
        return rawTags.split(",").map { it.trim().lowercase() }.filter { it.isNotEmpty() }
    }

    private fun filterByClimbType(
        problems: List<ProblemData>,
        climbType: String,
    ): List<ProblemData> {
        return problems.filter { it.climbType == climbType }
    }

    private fun filterByTag(problems: List<ProblemData>, tag: String): List<ProblemData> {
        return problems.filter { it.tags.contains(tag) }
    }

    private fun filterByDifficultyRange(
        problems: List<ProblemData>,
        minGrade: String,
        maxGrade: String,
    ): List<ProblemData> {
        return problems.filter { problem ->
            if (problem.climbType == "BOULDER" && problem.difficulty.startsWith("V")) {
                val gradeNum = extractVGradeNumber(problem.difficulty)
                val minNum = extractVGradeNumber(minGrade)
                val maxNum = extractVGradeNumber(maxGrade)
                gradeNum in minNum..maxNum
            } else {
                true // Simplified for other grade systems
            }
        }
    }

    private fun mergeData(
        local: Map<String, String>,
        server: Map<String, String>,
    ): Map<String, String> {
        return (local.keys + server.keys).associateWith { key -> server[key] ?: local[key]!! }
    }

    private fun isValidBackup(backup: BackupData): Boolean {
        return backup.version == "2.0" &&
            backup.formatVersion == "2.0" &&
            backup.exportedAt.matches(Regex("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")) &&
            backup.dataCount >= 0
    }

    // Data classes for testing

    data class AttemptData(val result: String, val duration: Int)

    data class AttemptStatistics(
        val totalAttempts: Int,
        val successfulAttempts: Int,
        val successRate: Double,
        val averageDuration: Double,
    )

    data class ProblemData(
        val id: String,
        val name: String,
        val climbType: String,
        val difficulty: String,
        val tags: List<String>,
    )

    data class BackupData(
        val version: String,
        val formatVersion: String,
        val exportedAt: String,
        val dataCount: Int,
    )
}
