import Combine
import Foundation

@MainActor
class SyncService: ObservableObject {

    @Published var isSyncing = false
    @Published var lastSyncTime: Date?
    @Published var syncError: String?
    @Published var isConnected = false
    @Published var isTesting = false
    @Published var isOfflineMode = false

    @Published var providerType: SyncProviderType = .server {
        didSet {
            updateActiveProvider()
            userDefaults.set(providerType.rawValue, forKey: Keys.providerType)
        }
    }

    private var activeProvider: SyncProvider?

    private let userDefaults = UserDefaults.standard
    private let logTag = "SyncService"
    private var syncTask: Task<Void, Never>?

    private enum Keys {
        static let serverURL = "sync_server_url"
        static let authToken = "sync_auth_token"
        static let lastSyncTime = "last_sync_time"
        static let isConnected = "is_connected"
        static let autoSyncEnabled = "auto_sync_enabled"
        static let offlineMode = "offline_mode"
        static let providerType = "sync_provider_type"
    }

    // Legacy properties for compatibility with SettingsView
    var serverURL: String {
        get { userDefaults.string(forKey: Keys.serverURL) ?? "" }
        set {
            userDefaults.set(newValue, forKey: Keys.serverURL)
            // If active provider is server, it will pick up the change from UserDefaults
        }
    }

    var authToken: String {
        get { userDefaults.string(forKey: Keys.authToken) ?? "" }
        set { userDefaults.set(newValue, forKey: Keys.authToken) }
    }

    var isConfigured: Bool {
        return activeProvider?.isConfigured ?? false
    }

    var isAutoSyncEnabled: Bool {
        get { userDefaults.bool(forKey: Keys.autoSyncEnabled) }
        set { userDefaults.set(newValue, forKey: Keys.autoSyncEnabled) }
    }

    init() {
        isConnected = userDefaults.bool(forKey: Keys.isConnected)
        isAutoSyncEnabled = userDefaults.object(forKey: Keys.autoSyncEnabled) as? Bool ?? true
        isOfflineMode = userDefaults.bool(forKey: Keys.offlineMode)

        if let savedType = userDefaults.string(forKey: Keys.providerType),
           let type = SyncProviderType(rawValue: savedType) {
            self.providerType = type
        } else {
            self.providerType = .server // Default
        }

        updateActiveProvider()
    }

    private func updateActiveProvider() {
        switch providerType {
        case .server:
            activeProvider = ServerSyncProvider()
        case .iCloud:
            activeProvider = ICloudSyncProvider()
        case .none:
            activeProvider = nil
        }

        // Update status based on new provider
        if let provider = activeProvider {
            isConnected = provider.isConnected
            lastSyncTime = provider.lastSyncTime
        } else {
            isConnected = false
            lastSyncTime = nil
        }
    }

    func syncWithServer(dataManager: ClimbingDataManager) async throws {
        if isOfflineMode {
            AppLogger.info("Sync skipped: Offline mode is enabled.", tag: logTag)
            return
        }

        guard let provider = activeProvider else {
            if providerType == .none {
                return
            }
            throw SyncError.notConfigured
        }

        guard provider.isConfigured else {
            throw SyncError.notConfigured
        }

        // For server provider, we check connection. For others, maybe not needed or different check.
        if providerType == .server && !provider.isConnected {
             throw SyncError.notConnected
        }

        isSyncing = true
        syncError = nil

        defer {
            isSyncing = false
        }

        do {
            try await provider.sync(dataManager: dataManager)

            // Update last sync time
            self.lastSyncTime = provider.lastSyncTime
        } catch {
            syncError = error.localizedDescription
            throw error
        }
    }

    func testConnection() async throws {
        guard let provider = activeProvider else {
            AppLogger.error("Test connection failed: No active provider", tag: logTag)
            throw SyncError.notConfigured
        }

        isTesting = true
        defer { isTesting = false }

        try await provider.testConnection()

        isConnected = provider.isConnected
        userDefaults.set(isConnected, forKey: Keys.isConnected)
    }

    func triggerAutoSync(dataManager: ClimbingDataManager) {
        guard isConnected && isConfigured && isAutoSyncEnabled else {
            if isSyncing {
                isSyncing = false
            }
            return
        }

        guard !isSyncing else { return }

        syncTask?.cancel()

        syncTask = Task {
            try? await Task.sleep(for: .seconds(2))
            guard !Task.isCancelled else { return }

            do {
                try await syncWithServer(dataManager: dataManager)
            } catch {
                self.isSyncing = false
            }
        }
    }

    func forceSyncNow(dataManager: ClimbingDataManager) {
        guard isConnected && isConfigured else { return }

        syncTask?.cancel()
        syncTask = nil

        Task {
            do {
                try await syncWithServer(dataManager: dataManager)
            } catch {
                self.isSyncing = false
            }
        }
    }

    func disconnect() {
        activeProvider?.disconnect()

        syncTask?.cancel()
        syncTask = nil
        isSyncing = false
        isConnected = false
        lastSyncTime = nil
        syncError = nil

        // These are shared keys, so clearing them affects all providers if they use them
        // But disconnect() is usually user initiated action
        userDefaults.set(false, forKey: Keys.isConnected)
    }

    func clearConfiguration() {
        serverURL = ""
        authToken = ""
        lastSyncTime = nil
        isConnected = false
        isAutoSyncEnabled = true
        userDefaults.removeObject(forKey: Keys.lastSyncTime)
        userDefaults.removeObject(forKey: Keys.isConnected)
        userDefaults.removeObject(forKey: Keys.autoSyncEnabled)
        syncTask?.cancel()
        syncTask = nil

        activeProvider?.disconnect()
    }

    deinit {
        syncTask?.cancel()
    }
}
