//
//  BackupFormat.swift

import Foundation

// MARK: - Backup Format Specification v2.0

/// Root structure for Ascently backup data
struct DeletedItem: Codable, Hashable {
    let id: String
    let type: String  // "gym", "problem", "session", "attempt"
    let deletedAt: String
}

struct ClimbDataBackup: Codable {
    let exportedAt: String
    let version: String
    let formatVersion: String
    let gyms: [BackupGym]
    let problems: [BackupProblem]
    let sessions: [BackupClimbSession]
    let attempts: [BackupAttempt]
    let deletedItems: [DeletedItem]

    init(
        exportedAt: String,
        version: String = "2.0",
        formatVersion: String = "2.0",
        gyms: [BackupGym],
        problems: [BackupProblem],
        sessions: [BackupClimbSession],
        attempts: [BackupAttempt],
        deletedItems: [DeletedItem] = []
    ) {
        self.exportedAt = exportedAt
        self.version = version
        self.formatVersion = formatVersion
        self.gyms = gyms
        self.problems = problems
        self.sessions = sessions
        self.attempts = attempts
        self.deletedItems = deletedItems
    }
}

// Platform-neutral gym representation for backup/restore
struct BackupGym: Codable {
    let id: String
    let name: String
    let location: String?
    let supportedClimbTypes: [ClimbType]
    let difficultySystems: [DifficultySystem]
    let customDifficultyGrades: [String]
    let notes: String?
    let createdAt: String
    let updatedAt: String

    init(from gym: Gym) {
        self.id = gym.id.uuidString
        self.name = gym.name
        self.location = gym.location
        self.supportedClimbTypes = gym.supportedClimbTypes
        self.difficultySystems = gym.difficultySystems
        self.customDifficultyGrades = gym.customDifficultyGrades
        self.notes = gym.notes

        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        self.createdAt = formatter.string(from: gym.createdAt)
        self.updatedAt = formatter.string(from: gym.updatedAt)
    }

    init(
        id: String,
        name: String,
        location: String?,
        supportedClimbTypes: [ClimbType],
        difficultySystems: [DifficultySystem],
        customDifficultyGrades: [String] = [],
        notes: String?,
        createdAt: String,
        updatedAt: String
    ) {
        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
    }

    func toGym() throws -> Gym {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

        guard let uuid = UUID(uuidString: id),
            let createdDate = formatter.date(from: createdAt),
            let updatedDate = formatter.date(from: updatedAt)
        else {
            throw BackupError.invalidDateFormat
        }

        return Gym.fromImport(
            id: uuid,
            name: name,
            location: location,
            supportedClimbTypes: supportedClimbTypes,
            difficultySystems: difficultySystems,
            customDifficultyGrades: customDifficultyGrades,
            notes: notes,
            createdAt: createdDate,
            updatedAt: updatedDate
        )
    }
}

// Platform-neutral problem representation for backup/restore
struct BackupProblem: Codable {
    let id: String
    let gymId: String
    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: String?  // ISO 8601 format
    let notes: String?
    let createdAt: String
    let updatedAt: String

    init(from problem: Problem) {
        self.id = problem.id.uuidString
        self.gymId = problem.gymId.uuidString
        self.name = problem.name
        self.description = problem.description
        self.climbType = problem.climbType
        self.difficulty = problem.difficulty
        self.tags = problem.tags
        self.location = problem.location
        self.imagePaths = problem.imagePaths.isEmpty ? nil : problem.imagePaths
        self.isActive = problem.isActive
        self.notes = problem.notes

        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        self.dateSet = problem.dateSet.map { formatter.string(from: $0) }
        self.createdAt = formatter.string(from: problem.createdAt)
        self.updatedAt = formatter.string(from: problem.updatedAt)
    }

    init(
        id: String,
        gymId: String,
        name: String?,
        description: String?,
        climbType: ClimbType,
        difficulty: DifficultyGrade,
        tags: [String] = [],
        location: String?,
        imagePaths: [String]?,
        isActive: Bool,
        dateSet: String?,
        notes: String?,
        createdAt: String,
        updatedAt: String
    ) {
        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
    }

    func toProblem() throws -> Problem {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

        guard let uuid = UUID(uuidString: id),
            let gymUuid = UUID(uuidString: gymId),
            let createdDate = formatter.date(from: createdAt),
            let updatedDate = formatter.date(from: updatedAt)
        else {
            throw BackupError.invalidDateFormat
        }

        let dateSetDate = dateSet.flatMap { formatter.date(from: $0) }

        return Problem.fromImport(
            id: uuid,
            gymId: gymUuid,
            name: name,
            description: description,
            climbType: climbType,
            difficulty: difficulty,
            tags: tags,
            location: location,
            imagePaths: imagePaths ?? [],
            isActive: isActive,
            dateSet: dateSetDate,
            notes: notes,
            createdAt: createdDate,
            updatedAt: updatedDate
        )
    }

    func withUpdatedImagePaths(_ newImagePaths: [String]) -> BackupProblem {
        return BackupProblem(
            id: self.id,
            gymId: self.gymId,
            name: self.name,
            description: self.description,
            climbType: self.climbType,
            difficulty: self.difficulty,
            tags: self.tags,
            location: self.location,
            imagePaths: newImagePaths.isEmpty ? nil : newImagePaths,
            isActive: self.isActive,
            dateSet: self.dateSet,
            notes: self.notes,
            createdAt: self.createdAt,
            updatedAt: self.updatedAt
        )
    }
}

// Platform-neutral climb session representation for backup/restore
struct BackupClimbSession: Codable {
    let id: String
    let gymId: String
    let date: String  // ISO 8601 format
    let startTime: String?  // ISO 8601 format
    let endTime: String?  // ISO 8601 format
    let duration: Int64?  // Duration in seconds
    let status: SessionStatus
    let notes: String?
    let createdAt: String
    let updatedAt: String

    init(from session: ClimbSession) {
        self.id = session.id.uuidString
        self.gymId = session.gymId.uuidString
        self.status = session.status
        self.notes = session.notes

        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        self.date = formatter.string(from: session.date)
        self.startTime = session.startTime.map { formatter.string(from: $0) }
        self.endTime = session.endTime.map { formatter.string(from: $0) }
        self.duration = session.duration.map { Int64($0) }
        self.createdAt = formatter.string(from: session.createdAt)
        self.updatedAt = formatter.string(from: session.updatedAt)
    }

    init(
        id: String,
        gymId: String,
        date: String,
        startTime: String?,
        endTime: String?,
        duration: Int64?,
        status: SessionStatus,
        notes: String?,
        createdAt: String,
        updatedAt: String
    ) {
        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
    }

    func toClimbSession() throws -> ClimbSession {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

        guard let uuid = UUID(uuidString: id),
            let gymUuid = UUID(uuidString: gymId),
            let dateValue = formatter.date(from: date),
            let createdDate = formatter.date(from: createdAt),
            let updatedDate = formatter.date(from: updatedAt)
        else {
            throw BackupError.invalidDateFormat
        }

        let startTimeValue = startTime.flatMap { formatter.date(from: $0) }
        let endTimeValue = endTime.flatMap { formatter.date(from: $0) }
        let durationValue = duration.map { Int($0) }

        return ClimbSession.fromImport(
            id: uuid,
            gymId: gymUuid,
            date: dateValue,
            startTime: startTimeValue,
            endTime: endTimeValue,
            duration: durationValue,
            status: status,
            notes: notes,
            createdAt: createdDate,
            updatedAt: updatedDate
        )
    }
}

// Platform-neutral attempt representation for backup/restore
struct BackupAttempt: Codable {
    let id: String
    let sessionId: String
    let problemId: String
    let result: AttemptResult
    let highestHold: String?
    let notes: String?
    let duration: Int64?  // Duration in seconds
    let restTime: Int64?  // Rest time in seconds
    let timestamp: String
    let createdAt: String
    let updatedAt: String?

    init(from attempt: Attempt) {
        self.id = attempt.id.uuidString
        self.sessionId = attempt.sessionId.uuidString
        self.problemId = attempt.problemId.uuidString
        self.result = attempt.result
        self.highestHold = attempt.highestHold
        self.notes = attempt.notes
        self.duration = attempt.duration.map { Int64($0) }
        self.restTime = attempt.restTime.map { Int64($0) }

        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        self.timestamp = formatter.string(from: attempt.timestamp)
        self.createdAt = formatter.string(from: attempt.createdAt)
        self.updatedAt = formatter.string(from: attempt.updatedAt)
    }

    init(
        id: String,
        sessionId: String,
        problemId: String,
        result: AttemptResult,
        highestHold: String?,
        notes: String?,
        duration: Int64?,
        restTime: Int64?,
        timestamp: String,
        createdAt: String,
        updatedAt: String?
    ) {
        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
    }

    func toAttempt() throws -> Attempt {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

        guard let uuid = UUID(uuidString: id),
            let sessionUuid = UUID(uuidString: sessionId),
            let problemUuid = UUID(uuidString: problemId),
            let timestampDate = formatter.date(from: timestamp),
            let createdDate = formatter.date(from: createdAt)
        else {
            throw BackupError.invalidDateFormat
        }
        let updatedDateParsed = updatedAt.flatMap { formatter.date(from: $0) }
        let updatedDate = updatedDateParsed ?? createdDate

        let durationValue = duration.map { Int($0) }
        let restTimeValue = restTime.map { Int($0) }

        return Attempt.fromImport(
            id: uuid,
            sessionId: sessionUuid,
            problemId: problemUuid,
            result: result,
            highestHold: highestHold,
            notes: notes,
            duration: durationValue,
            restTime: restTimeValue,
            timestamp: timestampDate,
            createdAt: createdDate,
            updatedAt: updatedDate
        )
    }
}

// MARK: - Backup Format Errors

enum BackupError: LocalizedError {
    case invalidDateFormat
    case invalidUUID
    case missingRequiredField(String)
    case unsupportedFormatVersion(String)

    var errorDescription: String? {
        switch self {
        case .invalidDateFormat:
            return "Invalid date format in backup data"
        case .invalidUUID:
            return "Invalid UUID format in backup data"
        case .missingRequiredField(let field):
            return "Missing required field: \(field)"
        case .unsupportedFormatVersion(let version):
            return "Unsupported backup format version: \(version)"
        }
    }
}

// MARK: - Extensions

// MARK: - Helper Extensions for Optional Mapping

extension Optional {
    func map<T>(_ transform: (Wrapped) -> T) -> T? {
        return self.flatMap { .some(transform($0)) }
    }
}
