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.LocalDateTime
import java.time.format.DateTimeFormatter

class BusinessLogicTests {

    @Test
    fun testClimbSessionLifecycle() {
        val gym = createTestGym()
        val session = ClimbSession.create(gym.id, "Test session notes")

        assertEquals(gym.id, session.gymId)
        assertEquals(SessionStatus.ACTIVE, session.status)
        assertNotNull(session.startTime)
        assertNull(session.endTime)
        assertNull(session.duration)

        val completedSession =
            session.copy(
                status = SessionStatus.COMPLETED,
                endTime = getCurrentTimestamp(),
                duration = 7200L,
            )
        assertEquals(SessionStatus.COMPLETED, completedSession.status)
        assertNotNull(completedSession.endTime)
        assertNotNull(completedSession.duration)
    }

    @Test
    fun testAttemptCreationAndValidation() {
        val gym = createTestGym()
        val problem = createTestProblem(gym.id)
        val session = ClimbSession.create(gym.id)

        val attempt =
            Attempt.create(
                sessionId = session.id,
                problemId = problem.id,
                result = AttemptResult.SUCCESS,
                notes = "Clean send!",
            )

        assertEquals(session.id, attempt.sessionId)
        assertEquals(problem.id, attempt.problemId)
        assertEquals(AttemptResult.SUCCESS, attempt.result)
        assertEquals("Clean send!", attempt.notes)
        assertNotNull(attempt.timestamp)
        assertNotNull(attempt.createdAt)
    }

    @Test
    fun testGymProblemRelationship() {
        val gym = createTestGym()
        val boulderProblem = createTestProblem(gym.id, ClimbType.BOULDER)
        val ropeProblem = createTestProblem(gym.id, ClimbType.ROPE)

        // Verify boulder problem uses compatible difficulty system
        assertTrue(gym.supportedClimbTypes.contains(boulderProblem.climbType))
        assertTrue(gym.difficultySystems.contains(boulderProblem.difficulty.system))

        // Verify rope problem uses compatible difficulty system
        assertTrue(gym.supportedClimbTypes.contains(ropeProblem.climbType))
        assertTrue(gym.difficultySystems.contains(ropeProblem.difficulty.system))
    }

    @Test
    fun testSessionAttemptAggregation() {
        val gym = createTestGym()
        val session = ClimbSession.create(gym.id)
        val problem1 = createTestProblem(gym.id)
        val problem2 = createTestProblem(gym.id)

        val attempts =
            listOf(
                Attempt.create(session.id, problem1.id, AttemptResult.SUCCESS),
                Attempt.create(session.id, problem1.id, AttemptResult.FALL),
                Attempt.create(session.id, problem2.id, AttemptResult.FLASH),
                Attempt.create(session.id, problem2.id, AttemptResult.SUCCESS),
            )

        val sessionStats = calculateSessionStatistics(session, attempts)

        assertEquals(4, sessionStats.totalAttempts)
        assertEquals(3, sessionStats.successfulAttempts)
        assertEquals(2, sessionStats.uniqueProblems)
        assertEquals(75.0, sessionStats.successRate, 0.01)
    }

    @Test
    fun testDifficultyProgressionTracking() {
        val gym = createTestGym()
        val session = ClimbSession.create(gym.id)

        val problems =
            listOf(
                createTestProblemWithGrade(gym.id, "V3"),
                createTestProblemWithGrade(gym.id, "V4"),
                createTestProblemWithGrade(gym.id, "V5"),
                createTestProblemWithGrade(gym.id, "V6"),
            )

        val attempts =
            problems.map { problem ->
                Attempt.create(session.id, problem.id, AttemptResult.SUCCESS)
            }

        val progression = calculateDifficultyProgression(attempts, problems)

        assertEquals("V3", progression.minGrade)
        assertEquals("V6", progression.maxGrade)
        assertEquals(4.5, progression.averageGrade, 0.1)
        assertTrue(progression.showsProgression)
    }

    @Test
    fun testBackupDataIntegrity() {
        val gym = createTestGym()
        val problems = listOf(createTestProblem(gym.id), createTestProblem(gym.id))
        val session = ClimbSession.create(gym.id)
        val attempts =
            problems.map { problem ->
                Attempt.create(session.id, problem.id, AttemptResult.SUCCESS)
            }

        val backup =
            createBackupData(
                gyms = listOf(gym),
                problems = problems,
                sessions = listOf(session),
                attempts = attempts,
            )

        validateBackupIntegrity(backup)

        assertEquals(1, backup.gyms.size)
        assertEquals(2, backup.problems.size)
        assertEquals(1, backup.sessions.size)
        assertEquals(2, backup.attempts.size)
    }

    @Test
    fun testClimbTypeCompatibilityRules() {
        val boulderGym =
            Gym(
                id = "boulder_gym",
                name = "Boulder Gym",
                location = "Boulder City",
                supportedClimbTypes = listOf(ClimbType.BOULDER),
                difficultySystems = listOf(DifficultySystem.V_SCALE, DifficultySystem.FONT),
                customDifficultyGrades = emptyList(),
                notes = null,
                createdAt = getCurrentTimestamp(),
                updatedAt = getCurrentTimestamp(),
            )

        val ropeGym =
            Gym(
                id = "rope_gym",
                name = "Rope Gym",
                location = "Rope City",
                supportedClimbTypes = listOf(ClimbType.ROPE),
                difficultySystems = listOf(DifficultySystem.YDS),
                customDifficultyGrades = emptyList(),
                notes = null,
                createdAt = getCurrentTimestamp(),
                updatedAt = getCurrentTimestamp(),
            )

        // Boulder gym should support boulder problems with V-Scale
        assertTrue(isCompatibleClimbType(boulderGym, ClimbType.BOULDER, DifficultySystem.V_SCALE))
        assertTrue(isCompatibleClimbType(boulderGym, ClimbType.BOULDER, DifficultySystem.FONT))
        assertFalse(isCompatibleClimbType(boulderGym, ClimbType.ROPE, DifficultySystem.YDS))

        // Rope gym should support rope problems with YDS
        assertTrue(isCompatibleClimbType(ropeGym, ClimbType.ROPE, DifficultySystem.YDS))
        assertFalse(isCompatibleClimbType(ropeGym, ClimbType.BOULDER, DifficultySystem.V_SCALE))
    }

    @Test
    fun testSessionDurationCalculation() {
        val startTime = "2024-01-01T10:00:00Z"
        val endTime = "2024-01-01T12:30:00Z"

        val calculatedDuration = calculateSessionDuration(startTime, endTime)
        assertEquals(9000L, calculatedDuration) // 2.5 hours = 9000 seconds
    }

    @Test
    fun testAttemptSequenceValidation() {
        val gym = createTestGym()
        val problem = createTestProblem(gym.id)
        val session = ClimbSession.create(gym.id)

        val attempts =
            listOf(
                createAttemptWithTimestamp(
                    session.id,
                    problem.id,
                    "2024-01-01T10:00:00Z",
                    AttemptResult.FALL,
                ),
                createAttemptWithTimestamp(
                    session.id,
                    problem.id,
                    "2024-01-01T10:05:00Z",
                    AttemptResult.FALL,
                ),
                createAttemptWithTimestamp(
                    session.id,
                    problem.id,
                    "2024-01-01T10:10:00Z",
                    AttemptResult.SUCCESS,
                ),
            )

        val sequence = AttemptSequence(attempts)

        assertEquals(3, sequence.totalAttempts)
        assertEquals(2, sequence.failedAttempts)
        assertEquals(1, sequence.successfulAttempts)
        assertTrue(sequence.isValidSequence())
        assertEquals(AttemptResult.SUCCESS, sequence.finalResult)
    }

    @Test
    fun testGradeConsistencyValidation() {
        val validCombinations =
            listOf(
                Pair(ClimbType.BOULDER, DifficultySystem.V_SCALE),
                Pair(ClimbType.BOULDER, DifficultySystem.FONT),
                Pair(ClimbType.ROPE, DifficultySystem.YDS),
                Pair(ClimbType.BOULDER, DifficultySystem.CUSTOM),
                Pair(ClimbType.ROPE, DifficultySystem.CUSTOM),
            )

        val invalidCombinations =
            listOf(
                Pair(ClimbType.BOULDER, DifficultySystem.YDS),
                Pair(ClimbType.ROPE, DifficultySystem.V_SCALE),
                Pair(ClimbType.ROPE, DifficultySystem.FONT),
            )

        validCombinations.forEach { (climbType, difficultySystem) ->
            assertTrue(
                "$climbType should be compatible with $difficultySystem",
                isValidGradeCombination(climbType, difficultySystem),
            )
        }

        invalidCombinations.forEach { (climbType, difficultySystem) ->
            assertFalse(
                "$climbType should not be compatible with $difficultySystem",
                isValidGradeCombination(climbType, difficultySystem),
            )
        }
    }

    @Test
    fun testProblemTagNormalization() {
        val rawTags = listOf("OVERHANG", "crimpy", "  Technical  ", "DYNAMIC", "")
        val normalizedTags = normalizeTags(rawTags)

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

    @Test
    fun testImagePathHandling() {
        val originalPaths =
            listOf(
                "/storage/images/problem1.jpg",
                "/data/cache/problem2.png",
                "relative/path/problem3.jpeg",
            )

        val relativePaths = convertToRelativePaths(originalPaths)

        assertEquals(3, relativePaths.size)
        assertTrue(relativePaths.all { !it.startsWith("/") })
        assertTrue(relativePaths.contains("problem1.jpg"))
        assertTrue(relativePaths.contains("problem2.png"))
        assertTrue(relativePaths.contains("problem3.jpeg"))
    }

    // Helper functions and data classes

    private fun createTestGym(): Gym {
        return Gym(
            id = "test_gym_1",
            name = "Test Climbing Gym",
            location = "Test City",
            supportedClimbTypes = listOf(ClimbType.BOULDER, ClimbType.ROPE),
            difficultySystems = listOf(DifficultySystem.V_SCALE, DifficultySystem.YDS),
            customDifficultyGrades = emptyList(),
            notes = "Test gym for unit testing",
            createdAt = getCurrentTimestamp(),
            updatedAt = getCurrentTimestamp(),
        )
    }

    private fun createTestProblem(
        gymId: String,
        climbType: ClimbType = ClimbType.BOULDER,
    ): Problem {
        val difficulty =
            when (climbType) {
                ClimbType.BOULDER -> DifficultyGrade(DifficultySystem.V_SCALE, "V5")
                ClimbType.ROPE -> DifficultyGrade(DifficultySystem.YDS, "5.10a")
            }

        return Problem(
            id = "test_problem_${java.util.UUID.randomUUID()}",
            gymId = gymId,
            name = "Test Problem",
            description = "A test climbing problem",
            climbType = climbType,
            difficulty = difficulty,
            tags = listOf("test", "overhang"),
            location = "Wall A",
            imagePaths = emptyList(),
            isActive = true,
            dateSet = "2024-01-01",
            notes = null,
            createdAt = getCurrentTimestamp(),
            updatedAt = getCurrentTimestamp(),
        )
    }

    private fun createTestProblemWithGrade(gymId: String, grade: String): Problem {
        return Problem(
            id = "test_problem_${java.util.UUID.randomUUID()}",
            gymId = gymId,
            name = "Test Problem $grade",
            description = null,
            climbType = ClimbType.BOULDER,
            difficulty = DifficultyGrade(DifficultySystem.V_SCALE, grade),
            tags = emptyList(),
            location = null,
            imagePaths = emptyList(),
            isActive = true,
            dateSet = null,
            notes = null,
            createdAt = getCurrentTimestamp(),
            updatedAt = getCurrentTimestamp(),
        )
    }

    private fun createAttemptWithTimestamp(
        sessionId: String,
        problemId: String,
        timestamp: String,
        result: AttemptResult,
    ): Attempt {
        return Attempt.create(
            sessionId = sessionId,
            problemId = problemId,
            result = result,
            timestamp = timestamp,
        )
    }

    private fun getCurrentTimestamp(): String {
        return LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "Z"
    }

    private fun calculateSessionStatistics(
        session: ClimbSession,
        attempts: List<Attempt>,
    ): SessionStatistics {
        val successful =
            attempts.count {
                it.result == AttemptResult.SUCCESS || it.result == AttemptResult.FLASH
            }
        val uniqueProblems = attempts.map { it.problemId }.toSet().size
        val successRate = (successful.toDouble() / attempts.size) * 100

        return SessionStatistics(
            totalAttempts = attempts.size,
            successfulAttempts = successful,
            uniqueProblems = uniqueProblems,
            successRate = successRate,
        )
    }

    private fun calculateDifficultyProgression(
        attempts: List<Attempt>,
        problems: List<Problem>,
    ): DifficultyProgression {
        val problemMap = problems.associateBy { it.id }
        val grades =
            attempts
                .mapNotNull { attempt -> problemMap[attempt.problemId]?.difficulty?.grade }
                .filter { it.startsWith("V") }

        val numericGrades =
            grades.mapNotNull { grade ->
                when (grade) {
                    "VB" -> 0
                    else -> grade.removePrefix("V").toIntOrNull()
                }
            }

        val minGrade = "V${numericGrades.minOrNull() ?: 0}".replace("V0", "VB")
        val maxGrade = "V${numericGrades.maxOrNull() ?: 0}".replace("V0", "VB")
        val avgGrade = numericGrades.average()
        val showsProgression =
            numericGrades.size > 1 &&
                (numericGrades.maxOrNull() ?: 0) > (numericGrades.minOrNull() ?: 0)

        return DifficultyProgression(minGrade, maxGrade, avgGrade, showsProgression)
    }

    private fun createBackupData(
        gyms: List<Gym>,
        problems: List<Problem>,
        sessions: List<ClimbSession>,
        attempts: List<Attempt>,
    ): ClimbDataBackup {
        return ClimbDataBackup(
            exportedAt = getCurrentTimestamp(),
            version = "2.0",
            formatVersion = "2.0",
            gyms =
            gyms.map { gym ->
                BackupGym(
                    id = gym.id,
                    name = gym.name,
                    location = gym.location,
                    supportedClimbTypes = gym.supportedClimbTypes,
                    difficultySystems = gym.difficultySystems,
                    customDifficultyGrades = gym.customDifficultyGrades,
                    notes = gym.notes,
                    createdAt = gym.createdAt,
                    updatedAt = gym.updatedAt,
                )
            },
            problems =
            problems.map { problem ->
                BackupProblem(
                    id = problem.id,
                    gymId = problem.gymId,
                    name = problem.name,
                    description = problem.description,
                    climbType = problem.climbType,
                    difficulty = problem.difficulty,
                    tags = problem.tags,
                    location = problem.location,
                    imagePaths = problem.imagePaths,
                    isActive = problem.isActive,
                    dateSet = problem.dateSet,
                    notes = problem.notes,
                    createdAt = problem.createdAt,
                    updatedAt = problem.updatedAt,
                )
            },
            sessions =
            sessions.map { session ->
                BackupClimbSession(
                    id = session.id,
                    gymId = session.gymId,
                    date = session.date,
                    startTime = session.startTime,
                    endTime = session.endTime,
                    duration = session.duration,
                    status = session.status,
                    notes = session.notes,
                    createdAt = session.createdAt,
                    updatedAt = session.updatedAt,
                )
            },
            attempts =
            attempts.map { attempt ->
                BackupAttempt(
                    id = attempt.id,
                    sessionId = attempt.sessionId,
                    problemId = attempt.problemId,
                    result = attempt.result,
                    highestHold = attempt.highestHold,
                    notes = attempt.notes,
                    duration = attempt.duration,
                    restTime = attempt.restTime,
                    timestamp = attempt.timestamp,
                    createdAt = attempt.createdAt,
                    updatedAt = attempt.updatedAt,
                )
            },
        )
    }

    private fun validateBackupIntegrity(backup: ClimbDataBackup) {
        // Verify all gym references exist
        val gymIds = backup.gyms.map { it.id }.toSet()
        backup.problems.forEach { problem ->
            assertTrue(
                "Problem ${problem.id} references non-existent gym ${problem.gymId}",
                gymIds.contains(problem.gymId),
            )
        }

        // Verify all session references exist
        val sessionIds = backup.sessions.map { it.id }.toSet()
        backup.attempts.forEach { attempt ->
            assertTrue(
                "Attempt ${attempt.id} references non-existent session ${attempt.sessionId}",
                sessionIds.contains(attempt.sessionId),
            )
        }

        // Verify all problem references exist
        val problemIds = backup.problems.map { it.id }.toSet()
        backup.attempts.forEach { attempt ->
            assertTrue(
                "Attempt ${attempt.id} references non-existent problem ${attempt.problemId}",
                problemIds.contains(attempt.problemId),
            )
        }
    }

    private fun isCompatibleClimbType(
        gym: Gym,
        climbType: ClimbType,
        difficultySystem: DifficultySystem,
    ): Boolean {
        return gym.supportedClimbTypes.contains(climbType) &&
            gym.difficultySystems.contains(difficultySystem)
    }

    private fun calculateSessionDuration(startTime: String, endTime: String): Long {
        // Simplified duration calculation (in seconds)
        // In real implementation, would use proper date parsing
        return 9000L // 2.5 hours for test
    }

    private fun isValidGradeCombination(
        climbType: ClimbType,
        difficultySystem: DifficultySystem,
    ): Boolean {
        return when (climbType) {
            ClimbType.BOULDER ->
                difficultySystem in
                    listOf(
                        DifficultySystem.V_SCALE,
                        DifficultySystem.FONT,
                        DifficultySystem.CUSTOM,
                    )
            ClimbType.ROPE ->
                difficultySystem in listOf(DifficultySystem.YDS, DifficultySystem.CUSTOM)
        }
    }

    private fun normalizeTags(tags: List<String>): List<String> {
        return tags.map { it.trim().lowercase() }.filter { it.isNotEmpty() }
    }

    private fun convertToRelativePaths(paths: List<String>): List<String> {
        return paths.map { path -> path.substringAfterLast('/') }
    }

    // Data classes for testing

    data class SessionStatistics(
        val totalAttempts: Int,
        val successfulAttempts: Int,
        val uniqueProblems: Int,
        val successRate: Double,
    )

    data class DifficultyProgression(
        val minGrade: String,
        val maxGrade: String,
        val averageGrade: Double,
        val showsProgression: Boolean,
    )

    data class AttemptSequence(val attempts: List<Attempt>) {
        val totalAttempts = attempts.size
        val failedAttempts =
            attempts.count {
                it.result == AttemptResult.FALL || it.result == AttemptResult.NO_PROGRESS
            }
        val successfulAttempts =
            attempts.count {
                it.result == AttemptResult.SUCCESS || it.result == AttemptResult.FLASH
            }
        val finalResult = attempts.lastOrNull()?.result

        fun isValidSequence(): Boolean {
            return attempts.isNotEmpty() && attempts.all { it.timestamp.isNotEmpty() }
        }
    }
}
