package com.atridad.ascently

import com.atridad.ascently.data.format.*
import com.atridad.ascently.data.model.*
import org.junit.Assert.*
import org.junit.Test
import java.time.Instant
import java.time.format.DateTimeFormatter

class DataModelTests {

    @Test
    fun testClimbTypeEnumValues() {
        val expectedTypes = setOf("ROPE", "BOULDER")
        val actualTypes = ClimbType.entries.map { it.name }.toSet()
        assertEquals(expectedTypes, actualTypes)
    }

    @Test
    fun testClimbTypeDisplayNames() {
        assertEquals("Rope", ClimbType.ROPE.displayName)
        assertEquals("Bouldering", ClimbType.BOULDER.displayName)
    }

    @Test
    fun testDifficultySystemEnumValues() {
        val systems = DifficultySystem.entries
        assertTrue(systems.contains(DifficultySystem.V_SCALE))
        assertTrue(systems.contains(DifficultySystem.YDS))
        assertTrue(systems.contains(DifficultySystem.FONT))
        assertTrue(systems.contains(DifficultySystem.CUSTOM))
        assertEquals(4, systems.size)
    }

    @Test
    fun testDifficultySystemDisplayNames() {
        assertEquals("V Scale", DifficultySystem.V_SCALE.displayName)
        assertEquals("YDS (Yosemite)", DifficultySystem.YDS.displayName)
        assertEquals("Font Scale", DifficultySystem.FONT.displayName)
        assertEquals("Custom", DifficultySystem.CUSTOM.displayName)
    }

    @Test
    fun testDifficultySystemClimbTypeCompatibility() {
        // Test bouldering systems
        assertTrue(DifficultySystem.V_SCALE.isBoulderingSystem)
        assertTrue(DifficultySystem.FONT.isBoulderingSystem)
        assertFalse(DifficultySystem.YDS.isBoulderingSystem)
        assertTrue(DifficultySystem.CUSTOM.isBoulderingSystem)

        // Test rope systems
        assertTrue(DifficultySystem.YDS.isRopeSystem)
        assertFalse(DifficultySystem.V_SCALE.isRopeSystem)
        assertFalse(DifficultySystem.FONT.isRopeSystem)
        assertTrue(DifficultySystem.CUSTOM.isRopeSystem)
    }

    @Test
    fun testDifficultySystemAvailableGrades() {
        val vScaleGrades = DifficultySystem.V_SCALE.availableGrades
        assertTrue(vScaleGrades.contains("VB"))
        assertTrue(vScaleGrades.contains("V0"))
        assertTrue(vScaleGrades.contains("V17"))
        assertEquals("VB", vScaleGrades.first())

        val ydsGrades = DifficultySystem.YDS.availableGrades
        assertTrue(ydsGrades.contains("5.0"))
        assertTrue(ydsGrades.contains("5.15d"))
        assertTrue(ydsGrades.contains("5.10a"))

        val fontGrades = DifficultySystem.FONT.availableGrades
        assertTrue(fontGrades.contains("3"))
        assertTrue(fontGrades.contains("8C+"))
        assertTrue(fontGrades.contains("6A"))

        val customGrades = DifficultySystem.CUSTOM.availableGrades
        assertTrue(customGrades.isEmpty())
    }

    @Test
    fun testDifficultySystemsForClimbType() {
        val boulderSystems = DifficultySystem.systemsForClimbType(ClimbType.BOULDER)
        assertTrue(boulderSystems.contains(DifficultySystem.V_SCALE))
        assertTrue(boulderSystems.contains(DifficultySystem.FONT))
        assertTrue(boulderSystems.contains(DifficultySystem.CUSTOM))
        assertFalse(boulderSystems.contains(DifficultySystem.YDS))

        val ropeSystems = DifficultySystem.systemsForClimbType(ClimbType.ROPE)
        assertTrue(ropeSystems.contains(DifficultySystem.YDS))
        assertTrue(ropeSystems.contains(DifficultySystem.CUSTOM))
        assertFalse(ropeSystems.contains(DifficultySystem.V_SCALE))
        assertFalse(ropeSystems.contains(DifficultySystem.FONT))
    }

    @Test
    fun testDifficultyGradeCreation() {
        val grade = DifficultyGrade(DifficultySystem.V_SCALE, "V5")
        assertEquals(DifficultySystem.V_SCALE, grade.system)
        assertEquals("V5", grade.grade)
        assertEquals(5, grade.numericValue)
    }

    @Test
    fun testDifficultyGradeNumericValueCalculation() {
        val vbGrade = DifficultyGrade(DifficultySystem.V_SCALE, "VB")
        assertEquals(0, vbGrade.numericValue)

        val v5Grade = DifficultyGrade(DifficultySystem.V_SCALE, "V5")
        assertEquals(5, v5Grade.numericValue)

        val ydsGrade = DifficultyGrade(DifficultySystem.YDS, "5.9")
        assertTrue(ydsGrade.numericValue > 0)
    }

    @Test
    fun testDifficultyGradeComparison() {
        val v3 = DifficultyGrade(DifficultySystem.V_SCALE, "V3")
        val v5 = DifficultyGrade(DifficultySystem.V_SCALE, "V5")
        val vb = DifficultyGrade(DifficultySystem.V_SCALE, "VB")

        assertTrue(v3.compareTo(v5) < 0) // V3 is easier than V5
        assertTrue(v5.compareTo(v3) > 0) // V5 is harder than V3
        assertTrue(vb.compareTo(v3) < 0) // VB is easier than V3
        assertEquals(0, v3.compareTo(v3)) // Same grade
    }

    @Test
    fun testAttemptResultEnumValues() {
        val expectedResults = setOf("SUCCESS", "FALL", "NO_PROGRESS", "FLASH")
        val actualResults = AttemptResult.entries.map { it.name }.toSet()
        assertEquals(expectedResults, actualResults)
    }

    @Test
    fun testSessionStatusEnumValues() {
        val expectedStatuses = setOf("ACTIVE", "COMPLETED", "PAUSED")
        val actualStatuses = SessionStatus.entries.map { it.name }.toSet()
        assertEquals(expectedStatuses, actualStatuses)
    }

    @Test
    fun testBackupGymCreationAndValidation() {
        val gym =
            BackupGym(
                id = "gym123",
                name = "Test Climbing Gym",
                location = "Test City",
                supportedClimbTypes = listOf(ClimbType.BOULDER, ClimbType.ROPE),
                difficultySystems = listOf(DifficultySystem.V_SCALE, DifficultySystem.YDS),
                customDifficultyGrades = emptyList(),
                notes = "Great gym for beginners",
                createdAt = "2024-01-01T10:00:00Z",
                updatedAt = "2024-01-01T10:00:00Z",
            )

        assertEquals("gym123", gym.id)
        assertEquals("Test Climbing Gym", gym.name)
        assertEquals("Test City", gym.location)
        assertEquals(2, gym.supportedClimbTypes.size)
        assertTrue(gym.supportedClimbTypes.contains(ClimbType.BOULDER))
        assertTrue(gym.supportedClimbTypes.contains(ClimbType.ROPE))
        assertEquals(2, gym.difficultySystems.size)
        assertTrue(gym.difficultySystems.contains(DifficultySystem.V_SCALE))
        assertTrue(gym.difficultySystems.contains(DifficultySystem.YDS))
    }

    @Test
    fun testBackupProblemCreationAndValidation() {
        val problem =
            BackupProblem(
                id = "problem123",
                gymId = "gym123",
                name = "Test Problem",
                description = "A challenging boulder problem",
                climbType = ClimbType.BOULDER,
                difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V5"),
                tags = listOf("overhang", "crimpy"),
                location = "Wall A",
                imagePaths = listOf("image1.jpg", "image2.jpg"),
                isActive = true,
                dateSet = "2024-01-01",
                notes = "Watch the start holds",
                createdAt = "2024-01-01T10:00:00Z",
                updatedAt = "2024-01-01T10:00:00Z",
            )

        assertEquals("problem123", problem.id)
        assertEquals("gym123", problem.gymId)
        assertEquals("Test Problem", problem.name)
        assertEquals(ClimbType.BOULDER, problem.climbType)
        assertEquals("V5", problem.difficulty.grade)
        assertTrue(problem.isActive)
        assertEquals(2, problem.tags?.size ?: 0)
        assertEquals(2, problem.imagePaths?.size ?: 0)
    }

    @Test
    fun testBackupClimbSessionCreationAndValidation() {
        val session =
            BackupClimbSession(
                id = "session123",
                gymId = "gym123",
                date = "2024-01-01",
                startTime = "2024-01-01T10:00:00Z",
                endTime = "2024-01-01T12:00:00Z",
                duration = 7200,
                status = SessionStatus.COMPLETED,
                notes = "Great session today",
                createdAt = "2024-01-01T10:00:00Z",
                updatedAt = "2024-01-01T12:00:00Z",
            )

        assertEquals("session123", session.id)
        assertEquals("gym123", session.gymId)
        assertEquals("2024-01-01", session.date)
        assertEquals(SessionStatus.COMPLETED, session.status)
        assertEquals(7200L, session.duration)
    }

    @Test
    fun testBackupAttemptCreationAndValidation() {
        val attempt =
            BackupAttempt(
                id = "attempt123",
                sessionId = "session123",
                problemId = "problem123",
                result = AttemptResult.SUCCESS,
                highestHold = "Top",
                notes = "Stuck it on second try",
                duration = 300,
                restTime = 120,
                timestamp = "2024-01-01T10:30:00Z",
                createdAt = "2024-01-01T10:30:00Z",
                updatedAt = "2024-01-01T10:30:00Z",
            )

        assertEquals("attempt123", attempt.id)
        assertEquals("session123", attempt.sessionId)
        assertEquals("problem123", attempt.problemId)
        assertEquals(AttemptResult.SUCCESS, attempt.result)
        assertEquals("Top", attempt.highestHold)
        assertEquals(300L, attempt.duration)
        assertEquals(120L, attempt.restTime)
    }

    @Test
    fun testClimbDataBackupCreationAndValidation() {
        val backup =
            ClimbDataBackup(
                exportedAt = "2024-01-01T10:00:00Z",
                version = "2.0",
                formatVersion = "2.0",
                gyms = emptyList(),
                problems = emptyList(),
                sessions = emptyList(),
                attempts = emptyList(),
            )

        assertEquals("2.0", backup.version)
        assertEquals("2.0", backup.formatVersion)
        assertTrue(backup.gyms.isEmpty())
        assertTrue(backup.problems.isEmpty())
        assertTrue(backup.sessions.isEmpty())
        assertTrue(backup.attempts.isEmpty())
    }

    @Test
    fun testDateFormatValidation() {
        val validDate = "2024-01-01T10:00:00Z"
        val formatter = DateTimeFormatter.ISO_INSTANT

        try {
            val instant = Instant.from(formatter.parse(validDate))
            assertNotNull(instant)
        } catch (e: Exception) {
            fail("Should not throw exception for valid date: $e")
        }
    }

    @Test
    fun testSessionDurationCalculation() {
        val session =
            BackupClimbSession(
                id = "test",
                gymId = "gym1",
                date = "2024-01-01",
                startTime = "2024-01-01T10:00:00Z",
                endTime = "2024-01-01T12:00:00Z",
                duration = 7200,
                status = SessionStatus.COMPLETED,
                notes = null,
                createdAt = "2024-01-01T10:00:00Z",
                updatedAt = "2024-01-01T12:00:00Z",
            )

        assertEquals(7200L, session.duration)
        val hours = session.duration!! / 3600
        assertEquals(2L, hours)
    }

    @Test
    fun testEmptyCollectionsHandling() {
        val gym =
            BackupGym(
                id = "gym1",
                name = "Test Gym",
                location = null,
                supportedClimbTypes = emptyList(),
                difficultySystems = emptyList(),
                customDifficultyGrades = emptyList(),
                notes = null,
                createdAt = "2024-01-01T10:00:00Z",
                updatedAt = "2024-01-01T10:00:00Z",
            )

        assertTrue(gym.supportedClimbTypes?.isEmpty() ?: true)
        assertTrue(gym.difficultySystems?.isEmpty() ?: true)
        assertTrue(gym.customDifficultyGrades?.isEmpty() ?: true)
        assertNull(gym.location)
        assertNull(gym.notes)
    }

    @Test
    fun testNullableFieldsHandling() {
        val problem =
            BackupProblem(
                id = "problem1",
                gymId = "gym1",
                name = null,
                description = null,
                climbType = ClimbType.BOULDER,
                difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V1"),
                tags = emptyList(),
                location = null,
                imagePaths = null,
                isActive = true,
                dateSet = null,
                notes = null,
                createdAt = "2024-01-01T10:00:00Z",
                updatedAt = "2024-01-01T10:00:00Z",
            )

        assertNull(problem.name)
        assertNull(problem.description)
        assertNull(problem.location)
        assertNull(problem.dateSet)
        assertNull(problem.notes)
        assertTrue(problem.tags?.isEmpty() ?: true)
        assertNull(problem.imagePaths)
    }

    @Test
    fun testUniqueIdGeneration() {
        val id1 = java.util.UUID.randomUUID().toString()
        val id2 = java.util.UUID.randomUUID().toString()

        assertNotEquals(id1, id2)
        assertEquals(36, id1.length)
        assertTrue(id1.contains("-"))
    }

    @Test
    fun testBackupDataFormatValidation() {
        val testJson =
            """
        {
            "exportedAt": "2024-01-01T10:00:00Z",
            "version": "2.0",
            "formatVersion": "2.0",
            "gyms": [],
            "problems": [],
            "sessions": [],
            "attempts": []
        }
            """.trimIndent()

        assertTrue(testJson.contains("exportedAt"))
        assertTrue(testJson.contains("version"))
        assertTrue(testJson.contains("gyms"))
        assertTrue(testJson.contains("problems"))
        assertTrue(testJson.contains("sessions"))
        assertTrue(testJson.contains("attempts"))
    }

    @Test
    fun testDateTimeFormatting() {
        val currentTime = System.currentTimeMillis()
        assertTrue(currentTime > 0)

        val timeString = Instant.ofEpochMilli(currentTime).toString()
        assertTrue(timeString.isNotEmpty())
        assertTrue(timeString.contains("T"))
        assertTrue(timeString.endsWith("Z"))
    }

    @Test
    fun testClimbTypeAndDifficultySystemCompatibility() {
        // Test that V_SCALE works with BOULDER
        val boulderProblem =
            BackupProblem(
                id = "boulder1",
                gymId = "gym1",
                name = "Boulder Problem",
                description = null,
                climbType = ClimbType.BOULDER,
                difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V3"),
                tags = emptyList(),
                location = null,
                imagePaths = null,
                isActive = true,
                dateSet = null,
                notes = null,
                createdAt = "2024-01-01T10:00:00Z",
                updatedAt = "2024-01-01T10:00:00Z",
            )

        assertEquals(ClimbType.BOULDER, boulderProblem.climbType)
        assertEquals(DifficultySystem.V_SCALE, boulderProblem.difficulty.system)

        // Test that YDS works with ROPE
        val ropeProblem =
            BackupProblem(
                id = "rope1",
                gymId = "gym1",
                name = "Rope Problem",
                description = null,
                climbType = ClimbType.ROPE,
                difficulty = DifficultyGrade(DifficultySystem.YDS, "5.10a"),
                tags = emptyList(),
                location = null,
                imagePaths = null,
                isActive = true,
                dateSet = null,
                notes = null,
                createdAt = "2024-01-01T10:00:00Z",
                updatedAt = "2024-01-01T10:00:00Z",
            )

        assertEquals(ClimbType.ROPE, ropeProblem.climbType)
        assertEquals(DifficultySystem.YDS, ropeProblem.difficulty.system)
    }

    @Test
    fun testStringOperations() {
        val problemName = "  Test Problem V5  "
        val trimmedName = problemName.trim()
        val uppercaseName = trimmedName.uppercase()
        val lowercaseName = trimmedName.lowercase()

        assertEquals("Test Problem V5", trimmedName)
        assertEquals("TEST PROBLEM V5", uppercaseName)
        assertEquals("test problem v5", lowercaseName)

        val components = trimmedName.split(" ")
        assertEquals(3, components.size)
        assertEquals("V5", components.last())
    }

    @Test
    fun testNumericOperations() {
        val grades = listOf(3, 5, 7, 4, 6)
        val sum = grades.sum()
        val average = grades.average()
        val maxGrade = grades.maxOrNull() ?: 0
        val minGrade = grades.minOrNull() ?: 0

        assertEquals(25, sum)
        assertEquals(5.0, average, 0.01)
        assertEquals(7, maxGrade)
        assertEquals(3, minGrade)
    }

    @Test
    fun testAttemptResultValidation() {
        val validResults =
            listOf(
                AttemptResult.SUCCESS,
                AttemptResult.FALL,
                AttemptResult.NO_PROGRESS,
                AttemptResult.FLASH,
            )

        assertEquals(4, validResults.size)
        assertTrue(validResults.contains(AttemptResult.SUCCESS))
        assertTrue(validResults.contains(AttemptResult.FALL))
        assertTrue(validResults.contains(AttemptResult.NO_PROGRESS))
        assertTrue(validResults.contains(AttemptResult.FLASH))
    }

    @Test
    fun testSessionStatusValidation() {
        val validStatuses =
            listOf(SessionStatus.ACTIVE, SessionStatus.COMPLETED, SessionStatus.PAUSED)

        assertEquals(3, validStatuses.size)
        assertTrue(validStatuses.contains(SessionStatus.ACTIVE))
        assertTrue(validStatuses.contains(SessionStatus.COMPLETED))
        assertTrue(validStatuses.contains(SessionStatus.PAUSED))
    }

    @Test
    fun testClimbDataIntegrity() {
        val gym =
            BackupGym(
                id = "gym1",
                name = "Test Gym",
                location = "Test City",
                supportedClimbTypes = listOf(ClimbType.BOULDER),
                difficultySystems = listOf(DifficultySystem.V_SCALE),
                customDifficultyGrades = emptyList(),
                notes = null,
                createdAt = "2024-01-01T10:00:00Z",
                updatedAt = "2024-01-01T10:00:00Z",
            )

        val problem =
            BackupProblem(
                id = "problem1",
                gymId = gym.id,
                name = "Test Problem",
                description = null,
                climbType = ClimbType.BOULDER,
                difficulty = DifficultyGrade(DifficultySystem.V_SCALE, "V3"),
                tags = emptyList(),
                location = null,
                imagePaths = null,
                isActive = true,
                dateSet = null,
                notes = null,
                createdAt = "2024-01-01T10:00:00Z",
                updatedAt = "2024-01-01T10:00:00Z",
            )

        val session =
            BackupClimbSession(
                id = "session1",
                gymId = gym.id,
                date = "2024-01-01",
                startTime = "2024-01-01T10:00:00Z",
                endTime = "2024-01-01T11:00:00Z",
                duration = 3600,
                status = SessionStatus.COMPLETED,
                notes = null,
                createdAt = "2024-01-01T10:00:00Z",
                updatedAt = "2024-01-01T11:00:00Z",
            )

        val attempt =
            BackupAttempt(
                id = "attempt1",
                sessionId = session.id,
                problemId = problem.id,
                result = AttemptResult.SUCCESS,
                highestHold = null,
                notes = null,
                duration = 120,
                restTime = null,
                timestamp = "2024-01-01T10:30:00Z",
                createdAt = "2024-01-01T10:30:00Z",
                updatedAt = "2024-01-01T10:30:00Z",
            )

        // Verify referential integrity
        assertEquals(gym.id, problem.gymId)
        assertEquals(gym.id, session.gymId)
        assertEquals(session.id, attempt.sessionId)
        assertEquals(problem.id, attempt.problemId)

        // Verify climb type compatibility
        assertTrue(gym.supportedClimbTypes.contains(problem.climbType))
        assertTrue(gym.difficultySystems.contains(problem.difficulty.system))
    }
}
