import APIClient
import Dependencies
import Errors
import Utilities
import RequestState
import Sharing

let log = Logger(category: "UserConfigClient")

public protocol UserConfigClientProtocol {
    @discardableResult
    func fetch() async -> Result<UserConfigSchema, AnyError>?

    @discardableResult
    func update(_ userConfig: UserConfigSchema) async -> Result<Void, AnyError>

    var userConfig: UserConfigSchema? { get }
}

/// Responsible for fetching, updating and centralizing access to userConfig
public struct UserConfigClient: UserConfigClientProtocol {
    @Dependency(\.apiClientV2) private var api

    // In-memory cache so we don't fetch twice
    @Shared(.inMemory(.userConfig)) private var cachedUserConfig: UserConfigSchema?

    public init() {}

    public var userConfig: UserConfigSchema? {
        cachedUserConfig
    }

    public func fetch() async -> Result<UserConfigSchema, AnyError>? {
        if let userConfig = cachedUserConfig {
            return .success(userConfig)
        }

        let result = await Result { try await api.getUserConfig() }
            .mapError(AnyError.init)

        switch result {
        case let .success(userConfig):
            log.debug("Successfully fetched user config.")
            $cachedUserConfig.withLock { $0 = userConfig }

        case let .failure(error):
            log.telemetry.error(error, message: "Failed to fetch user config.")
        }

        return result
    }

    public func update(_ userConfig: UserConfigSchema) async -> Result<Void, AnyError> {
        let result = await Result { try await api.updateUserConfig(userConfig) }
            .mapError(AnyError.init)

        switch result {
        case .success:
            log.debug("Successfully updated user config.")
            $cachedUserConfig.withLock { $0 = userConfig }

        case let .failure(error):
            log.telemetry.error(error, message: "Failed to update user config.")
        }

        return result
    }
}

private extension String {
    static let userConfig = "userConfig"
}

private struct NoOpUserConfigClient: UserConfigClientProtocol {
    func fetch() async -> Result<UserConfigSchema, AnyError>? {
        .none
    }

    func update(_: UserConfigSchema) async -> Result<Void, AnyError> {
        .success(())
    }

    var userConfig: UserConfigSchema? {
        .none
    }
}

private enum UserConfigClientKey: DependencyKey {
    static let liveValue: UserConfigClientProtocol = UserConfigClient()
    static let previewValue: UserConfigClientProtocol = NoOpUserConfigClient()
    static let testValue: UserConfigClientProtocol = NoOpUserConfigClient()
}

public extension DependencyValues {
    var userConfigClient: UserConfigClientProtocol {
        get { self[UserConfigClientKey.self] }
        set { self[UserConfigClientKey.self] = newValue }
    }
}
