import Foundation

struct SyncMerger {
    private static let logTag = "SyncMerger"
    
    static func mergeDataSafely(
        localBackup: ClimbDataBackup,
        serverBackup: ClimbDataBackup,
        dataManager: ClimbingDataManager,
        imagePathMapping: [String: String]
    ) throws -> (gyms: [Gym], problems: [Problem], sessions: [ClimbSession], attempts: [Attempt], uniqueDeletions: [DeletedItem]) {
        
        // Merge deletion lists first to prevent resurrection of deleted items
        let localDeletions = dataManager.getDeletedItems()
        let allDeletions = localDeletions + serverBackup.deletedItems
        let uniqueDeletions = Array(Set(allDeletions))
        
        AppLogger.info("Merging gyms...", tag: logTag)
        let mergedGyms = mergeGyms(
            local: dataManager.gyms,
            server: serverBackup.gyms,
            deletedItems: uniqueDeletions)
        
        AppLogger.info("Merging problems...", tag: logTag)
        let mergedProblems = try mergeProblems(
            local: dataManager.problems,
            server: serverBackup.problems,
            imagePathMapping: imagePathMapping,
            deletedItems: uniqueDeletions)
        
        AppLogger.info("Merging sessions...", tag: logTag)
        let mergedSessions = try mergeSessions(
            local: dataManager.sessions,
            server: serverBackup.sessions,
            deletedItems: uniqueDeletions)
        
        AppLogger.info("Merging attempts...", tag: logTag)
        let mergedAttempts = try mergeAttempts(
            local: dataManager.attempts,
            server: serverBackup.attempts,
            deletedItems: uniqueDeletions)
            
        return (mergedGyms, mergedProblems, mergedSessions, mergedAttempts, uniqueDeletions)
    }
    
    private static func mergeGyms(local: [Gym], server: [BackupGym], deletedItems: [DeletedItem]) -> [Gym] {
        var merged = local
        let deletedGymIds = Set(deletedItems.filter { $0.type == "gym" }.map { $0.id })
        let localGymIds = Set(local.map { $0.id.uuidString })
        
        merged.removeAll { deletedGymIds.contains($0.id.uuidString) }
        
        // Add new items from server (excluding deleted ones)
        for serverGym in server {
            if let serverGymConverted = try? serverGym.toGym() {
                let localHasGym = localGymIds.contains(serverGym.id)
                let isDeleted = deletedGymIds.contains(serverGym.id)
                
                if !localHasGym && !isDeleted {
                    merged.append(serverGymConverted)
                }
            }
        }
        
        return merged
    }
    
    private static func mergeProblems(
        local: [Problem],
        server: [BackupProblem],
        imagePathMapping: [String: String],
        deletedItems: [DeletedItem]
    ) throws -> [Problem] {
        var merged = local
        let deletedProblemIds = Set(deletedItems.filter { $0.type == "problem" }.map { $0.id })
        let localProblemIds = Set(local.map { $0.id.uuidString })
        
        merged.removeAll { deletedProblemIds.contains($0.id.uuidString) }
        
        for serverProblem in server {
            let localHasProblem = localProblemIds.contains(serverProblem.id)
            let isDeleted = deletedProblemIds.contains(serverProblem.id)
            
            if !localHasProblem && !isDeleted {
                var problemToAdd = serverProblem
                
                if !imagePathMapping.isEmpty, let imagePaths = serverProblem.imagePaths, !imagePaths.isEmpty {
                    let updatedImagePaths = imagePaths.compactMap { oldPath in
                        imagePathMapping[oldPath] ?? oldPath
                    }
                    if updatedImagePaths != imagePaths {
                        problemToAdd = BackupProblem(
                            id: serverProblem.id,
                            gymId: serverProblem.gymId,
                            name: serverProblem.name,
                            description: serverProblem.description,
                            climbType: serverProblem.climbType,
                            difficulty: serverProblem.difficulty,
                            tags: serverProblem.tags,
                            location: serverProblem.location,
                            imagePaths: updatedImagePaths,
                            isActive: serverProblem.isActive,
                            dateSet: serverProblem.dateSet,
                            notes: serverProblem.notes,
                            createdAt: serverProblem.createdAt,
                            updatedAt: serverProblem.updatedAt
                        )
                    }
                }
                
                if let serverProblemConverted = try? problemToAdd.toProblem() {
                    merged.append(serverProblemConverted)
                }
            }
        }
        
        return merged
    }
    
    private static func mergeSessions(
        local: [ClimbSession], server: [BackupClimbSession], deletedItems: [DeletedItem]
    ) throws -> [ClimbSession] {
        var merged = local
        let deletedSessionIds = Set(deletedItems.filter { $0.type == "session" }.map { $0.id })
        let localSessionIds = Set(local.map { $0.id.uuidString })
        
        merged.removeAll { session in
            deletedSessionIds.contains(session.id.uuidString) && session.status != .active
        }
        
        for serverSession in server {
            let localHasSession = localSessionIds.contains(serverSession.id)
            let isDeleted = deletedSessionIds.contains(serverSession.id)
            
            if !localHasSession && !isDeleted {
                if let serverSessionConverted = try? serverSession.toClimbSession() {
                    merged.append(serverSessionConverted)
                }
            }
        }
        
        return merged
    }
    
    private static func mergeAttempts(
        local: [Attempt], server: [BackupAttempt], deletedItems: [DeletedItem]
    ) throws -> [Attempt] {
        var merged = local
        let deletedAttemptIds = Set(deletedItems.filter { $0.type == "attempt" }.map { $0.id })
        let localAttemptIds = Set(local.map { $0.id.uuidString })
        
        // Get active session IDs to protect their attempts
        let activeSessionIds = Set(
            local.compactMap { attempt in
                return attempt.sessionId
            }.filter { _ in
                return true
            })
        
        // Remove items that were deleted on other devices (but be conservative with attempts)
        merged.removeAll { attempt in
            deletedAttemptIds.contains(attempt.id.uuidString)
                && !activeSessionIds.contains(attempt.sessionId)
        }
        
        for serverAttempt in server {
            let localHasAttempt = localAttemptIds.contains(serverAttempt.id)
            let isDeleted = deletedAttemptIds.contains(serverAttempt.id)
            
            if !localHasAttempt && !isDeleted {
                if let serverAttemptConverted = try? serverAttempt.toAttempt() {
                    merged.append(serverAttemptConverted)
                }
            }
        }
        
        return merged
    }
}
