import Foundation
import SwiftUI

enum ClimbType: String, CaseIterable, Codable {
    case rope = "ROPE"
    case boulder = "BOULDER"

    var displayName: String {
        switch self {
        case .rope:
            return "Rope"
        case .boulder:
            return "Bouldering"
        }
    }
}

enum DifficultySystem: String, CaseIterable, Codable {
    case vScale = "V_SCALE"
    case font = "FONT"
    case yds = "YDS"
    case custom = "CUSTOM"

    var displayName: String {
        switch self {
        case .vScale:
            return "V Scale"
        case .font:
            return "Font Scale"
        case .yds:
            return "YDS (Yosemite)"
        case .custom:
            return "Custom"
        }
    }

    var isBoulderingSystem: Bool {
        switch self {
        case .vScale, .font:
            return true
        case .yds:
            return false
        case .custom:
            return true
        }
    }

    var isRopeSystem: Bool {
        switch self {
        case .yds:
            return true
        case .vScale, .font:
            return false
        case .custom:
            return true
        }
    }

    var availableGrades: [String] {
        switch self {
        case .vScale:
            return [
                "VB", "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9", "V10", "V11",
                "V12", "V13", "V14", "V15", "V16", "V17",
            ]
        case .font:
            return [
                "3", "4A", "4B", "4C", "5A", "5B", "5C", "6A", "6A+", "6B", "6B+", "6C", "6C+",
                "7A", "7A+", "7B", "7B+", "7C", "7C+", "8A", "8A+", "8B", "8B+", "8C", "8C+",
            ]
        case .yds:
            return [
                "5.0", "5.1", "5.2", "5.3", "5.4", "5.5", "5.6", "5.7", "5.8", "5.9", "5.10a",
                "5.10b", "5.10c", "5.10d", "5.11a", "5.11b", "5.11c", "5.11d", "5.12a", "5.12b",
                "5.12c", "5.12d", "5.13a", "5.13b", "5.13c", "5.13d", "5.14a", "5.14b", "5.14c",
                "5.14d", "5.15a", "5.15b", "5.15c", "5.15d",
            ]
        case .custom:
            return []
        }
    }

    static func systemsForClimbType(_ climbType: ClimbType) -> [DifficultySystem] {
        switch climbType {
        case .boulder:
            return allCases.filter { $0.isBoulderingSystem }
        case .rope:
            return allCases.filter { $0.isRopeSystem }
        }
    }
}

enum AttemptResult: String, CaseIterable, Codable {
    case success = "SUCCESS"
    case fall = "FALL"
    case noProgress = "NO_PROGRESS"
    case flash = "FLASH"

    var displayName: String {
        switch self {
        case .success:
            return "Success"
        case .fall:
            return "Fall"
        case .noProgress:
            return "No Progress"
        case .flash:
            return "Flash"
        }
    }

    var isSuccessful: Bool {
        return self == .success || self == .flash
    }
}

enum SessionStatus: String, CaseIterable, Codable {
    case active = "ACTIVE"
    case completed = "COMPLETED"
    case paused = "PAUSED"

    var displayName: String {
        switch self {
        case .active:
            return "Active"
        case .completed:
            return "Completed"
        case .paused:
            return "Paused"
        }
    }
}

struct DifficultyGrade: Codable, Hashable {
    let system: DifficultySystem
    let grade: String
    let numericValue: Int

    init(system: DifficultySystem, grade: String) {
        self.system = system
        self.grade = grade
        self.numericValue = Self.calculateNumericValue(system: system, grade: grade)
    }

    private static func calculateNumericValue(system: DifficultySystem, grade: String) -> Int {
        switch system {
        case .vScale:
            if grade == "VB" { return 0 }
            return Int(grade.replacingOccurrences(of: "V", with: "")) ?? 0
        case .font:
            let fontMapping: [String: Int] = [
                "3": 3, "4A": 4, "4B": 5, "4C": 6, "5A": 7, "5B": 8, "5C": 9,
                "6A": 10, "6A+": 11, "6B": 12, "6B+": 13, "6C": 14, "6C+": 15,
                "7A": 16, "7A+": 17, "7B": 18, "7B+": 19, "7C": 20, "7C+": 21,
                "8A": 22, "8A+": 23, "8B": 24, "8B+": 25, "8C": 26, "8C+": 27,
            ]
            return fontMapping[grade] ?? 0
        case .yds:
            let ydsMapping: [String: Int] = [
                "5.0": 50, "5.1": 51, "5.2": 52, "5.3": 53, "5.4": 54, "5.5": 55,
                "5.6": 56, "5.7": 57, "5.8": 58, "5.9": 59, "5.10a": 60, "5.10b": 61,
                "5.10c": 62, "5.10d": 63, "5.11a": 64, "5.11b": 65, "5.11c": 66,
                "5.11d": 67, "5.12a": 68, "5.12b": 69, "5.12c": 70, "5.12d": 71,
                "5.13a": 72, "5.13b": 73, "5.13c": 74, "5.13d": 75, "5.14a": 76,
                "5.14b": 77, "5.14c": 78, "5.14d": 79, "5.15a": 80, "5.15b": 81,
                "5.15c": 82, "5.15d": 83,
            ]
            return ydsMapping[grade] ?? 0
        case .custom:
            return Int(grade) ?? 0
        }
    }
}

struct Gym: Identifiable, Codable, Hashable {
    let id: UUID
    let name: String
    let location: String?
    let supportedClimbTypes: [ClimbType]
    let difficultySystems: [DifficultySystem]
    let customDifficultyGrades: [String]
    let notes: String?
    let createdAt: Date
    let updatedAt: Date

    init(
        name: String, location: String? = nil, supportedClimbTypes: [ClimbType],
        difficultySystems: [DifficultySystem], customDifficultyGrades: [String] = [],
        notes: String? = nil
    ) {
        self.id = UUID()
        self.name = name
        self.location = location
        self.supportedClimbTypes = supportedClimbTypes
        self.difficultySystems = difficultySystems
        self.customDifficultyGrades = customDifficultyGrades
        self.notes = notes
        let now = Date()
        self.createdAt = now
        self.updatedAt = now
    }

    func updated(
        name: String? = nil, location: String? = nil, supportedClimbTypes: [ClimbType]? = nil,
        difficultySystems: [DifficultySystem]? = nil, customDifficultyGrades: [String]? = nil,
        notes: String? = nil
    ) -> Gym {
        return Gym(
            id: self.id,
            name: name ?? self.name,
            location: location ?? self.location,
            supportedClimbTypes: supportedClimbTypes ?? self.supportedClimbTypes,
            difficultySystems: difficultySystems ?? self.difficultySystems,
            customDifficultyGrades: customDifficultyGrades ?? self.customDifficultyGrades,
            notes: notes ?? self.notes,
            createdAt: self.createdAt,
            updatedAt: Date()
        )
    }

    private init(
        id: UUID, name: String, location: String?, supportedClimbTypes: [ClimbType],
        difficultySystems: [DifficultySystem], customDifficultyGrades: [String], notes: String?,
        createdAt: Date, updatedAt: Date
    ) {
        self.id = id
        self.name = name
        self.location = location
        self.supportedClimbTypes = supportedClimbTypes
        self.difficultySystems = difficultySystems
        self.customDifficultyGrades = customDifficultyGrades
        self.notes = notes
        self.createdAt = createdAt
        self.updatedAt = updatedAt
    }

    static func fromImport(
        id: UUID, name: String, location: String?, supportedClimbTypes: [ClimbType],
        difficultySystems: [DifficultySystem], customDifficultyGrades: [String], notes: String?,
        createdAt: Date, updatedAt: Date
    ) -> Gym {
        return Gym(
            id: id,
            name: name,
            location: location,
            supportedClimbTypes: supportedClimbTypes,
            difficultySystems: difficultySystems,
            customDifficultyGrades: customDifficultyGrades,
            notes: notes,
            createdAt: createdAt,
            updatedAt: updatedAt
        )
    }
}

struct Problem: Identifiable, Codable, Hashable {
    let id: UUID
    let gymId: UUID
    let name: String?
    let description: String?
    let climbType: ClimbType
    let difficulty: DifficultyGrade

    let tags: [String]
    let location: String?
    let imagePaths: [String]
    let isActive: Bool
    let dateSet: Date?
    let notes: String?
    let createdAt: Date
    let updatedAt: Date

    init(
        gymId: UUID, name: String? = nil, description: String? = nil, climbType: ClimbType,
        difficulty: DifficultyGrade, tags: [String] = [],
        location: String? = nil, imagePaths: [String] = [], dateSet: Date? = nil,
        notes: String? = nil
    ) {
        self.id = UUID()
        self.gymId = gymId
        self.name = name
        self.description = description
        self.climbType = climbType
        self.difficulty = difficulty

        self.tags = tags
        self.location = location
        self.imagePaths = imagePaths
        self.isActive = true
        self.dateSet = dateSet
        self.notes = notes
        let now = Date()
        self.createdAt = now
        self.updatedAt = now
    }

    func updated(
        name: String? = nil, description: String? = nil, climbType: ClimbType? = nil,
        difficulty: DifficultyGrade? = nil, tags: [String]? = nil,
        location: String? = nil, imagePaths: [String]? = nil, isActive: Bool? = nil,
        dateSet: Date? = nil, notes: String? = nil
    ) -> Problem {
        return Problem(
            id: self.id,
            gymId: self.gymId,
            name: name ?? self.name,
            description: description ?? self.description,
            climbType: climbType ?? self.climbType,
            difficulty: difficulty ?? self.difficulty,

            tags: tags ?? self.tags,
            location: location ?? self.location,
            imagePaths: imagePaths ?? self.imagePaths,
            isActive: isActive ?? self.isActive,
            dateSet: dateSet ?? self.dateSet,
            notes: notes ?? self.notes,
            createdAt: self.createdAt,
            updatedAt: Date()
        )
    }

    private init(
        id: UUID, gymId: UUID, name: String?, description: String?, climbType: ClimbType,
        difficulty: DifficultyGrade, tags: [String], location: String?,
        imagePaths: [String], isActive: Bool, dateSet: Date?, notes: String?, createdAt: Date,
        updatedAt: Date
    ) {
        self.id = id
        self.gymId = gymId
        self.name = name
        self.description = description
        self.climbType = climbType
        self.difficulty = difficulty

        self.tags = tags
        self.location = location
        self.imagePaths = imagePaths
        self.isActive = isActive
        self.dateSet = dateSet
        self.notes = notes
        self.createdAt = createdAt
        self.updatedAt = updatedAt
    }

    static func fromImport(
        id: UUID, gymId: UUID, name: String?, description: String?, climbType: ClimbType,
        difficulty: DifficultyGrade, tags: [String], location: String?,
        imagePaths: [String], isActive: Bool, dateSet: Date?, notes: String?, createdAt: Date,
        updatedAt: Date
    ) -> Problem {
        return Problem(
            id: id,
            gymId: gymId,
            name: name,
            description: description,
            climbType: climbType,
            difficulty: difficulty,

            tags: tags,
            location: location,
            imagePaths: imagePaths,
            isActive: isActive,
            dateSet: dateSet,
            notes: notes,
            createdAt: createdAt,
            updatedAt: updatedAt
        )
    }
}

struct ClimbSession: Identifiable, Codable, Hashable {
    let id: UUID
    let gymId: UUID
    let date: Date
    let startTime: Date?
    let endTime: Date?
    let duration: Int?  // Duration in minutes
    let status: SessionStatus
    let notes: String?
    let createdAt: Date
    let updatedAt: Date

    init(gymId: UUID, notes: String? = nil) {
        self.id = UUID()
        self.gymId = gymId
        let now = Date()
        self.date = now
        self.startTime = now
        self.endTime = nil
        self.duration = nil
        self.status = .active
        self.notes = notes
        self.createdAt = now
        self.updatedAt = now
    }

    func completed() -> ClimbSession {
        let endTime = Date()
        let durationMinutes =
            startTime != nil ? Int(endTime.timeIntervalSince(startTime!) / 60) : nil

        return ClimbSession(
            id: self.id,
            gymId: self.gymId,
            date: self.date,
            startTime: self.startTime,
            endTime: endTime,
            duration: durationMinutes,
            status: .completed,
            notes: self.notes,
            createdAt: self.createdAt,
            updatedAt: Date()
        )
    }

    func updated(notes: String? = nil, status: SessionStatus? = nil) -> ClimbSession {
        return ClimbSession(
            id: self.id,
            gymId: self.gymId,
            date: self.date,
            startTime: self.startTime,
            endTime: self.endTime,
            duration: self.duration,
            status: status ?? self.status,
            notes: notes ?? self.notes,
            createdAt: self.createdAt,
            updatedAt: Date()
        )
    }

    private init(
        id: UUID, gymId: UUID, date: Date, startTime: Date?, endTime: Date?, duration: Int?,
        status: SessionStatus, notes: String?, createdAt: Date, updatedAt: Date
    ) {
        self.id = id
        self.gymId = gymId
        self.date = date
        self.startTime = startTime
        self.endTime = endTime
        self.duration = duration
        self.status = status
        self.notes = notes
        self.createdAt = createdAt
        self.updatedAt = updatedAt
    }

    static func fromImport(
        id: UUID, gymId: UUID, date: Date, startTime: Date?, endTime: Date?, duration: Int?,
        status: SessionStatus, notes: String?, createdAt: Date, updatedAt: Date
    ) -> ClimbSession {
        return ClimbSession(
            id: id,
            gymId: gymId,
            date: date,
            startTime: startTime,
            endTime: endTime,
            duration: duration,
            status: status,
            notes: notes,
            createdAt: createdAt,
            updatedAt: updatedAt
        )
    }
}

struct Attempt: Identifiable, Codable, Hashable {
    let id: UUID
    let sessionId: UUID
    let problemId: UUID
    let result: AttemptResult
    let highestHold: String?
    let notes: String?
    let duration: Int?
    let restTime: Int?
    let timestamp: Date
    let createdAt: Date
    let updatedAt: Date

    init(
        sessionId: UUID, problemId: UUID, result: AttemptResult, highestHold: String? = nil,
        notes: String? = nil, duration: Int? = nil, restTime: Int? = nil, timestamp: Date = Date()
    ) {
        let now = Date()
        self.id = UUID()
        self.sessionId = sessionId
        self.problemId = problemId
        self.result = result
        self.highestHold = highestHold
        self.notes = notes
        self.duration = duration
        self.restTime = restTime
        self.timestamp = timestamp
        self.createdAt = now
        self.updatedAt = now
    }

    func updated(
        problemId: UUID? = nil, result: AttemptResult? = nil, highestHold: String? = nil,
        notes: String? = nil,
        duration: Int? = nil, restTime: Int? = nil
    ) -> Attempt {
        return Attempt(
            id: self.id,
            sessionId: self.sessionId,
            problemId: problemId ?? self.problemId,
            result: result ?? self.result,
            highestHold: highestHold ?? self.highestHold,
            notes: notes ?? self.notes,
            duration: duration ?? self.duration,
            restTime: restTime ?? self.restTime,
            timestamp: self.timestamp,
            createdAt: self.createdAt,
            updatedAt: Date()
        )
    }

    private init(
        id: UUID, sessionId: UUID, problemId: UUID, result: AttemptResult, highestHold: String?,
        notes: String?, duration: Int?, restTime: Int?, timestamp: Date, createdAt: Date,
        updatedAt: Date
    ) {
        self.id = id
        self.sessionId = sessionId
        self.problemId = problemId
        self.result = result
        self.highestHold = highestHold
        self.notes = notes
        self.duration = duration
        self.restTime = restTime
        self.timestamp = timestamp
        self.createdAt = createdAt
        self.updatedAt = updatedAt
    }

    static func fromImport(
        id: UUID, sessionId: UUID, problemId: UUID, result: AttemptResult, highestHold: String?,
        notes: String?, duration: Int?, restTime: Int?, timestamp: Date, createdAt: Date,
        updatedAt: Date
    ) -> Attempt {
        return Attempt(
            id: id,
            sessionId: sessionId,
            problemId: problemId,
            result: result,
            highestHold: highestHold,
            notes: notes,
            duration: duration,
            restTime: restTime,
            timestamp: timestamp,
            createdAt: createdAt,
            updatedAt: updatedAt
        )
    }
}

extension DifficultyGrade: Comparable {
    static func < (lhs: DifficultyGrade, rhs: DifficultyGrade) -> Bool {
        if lhs.system != rhs.system {
            return false  // Can't compare different systems
        }
        return lhs.numericValue < rhs.numericValue
    }
}
