import Foundation
import SwiftUI
import UIKit

struct AppUpdateNotice: Identifiable {
    enum Kind {
        case need
        case force
        case soft
    }

    let id = UUID()
    let kind: Kind
    let appStoreURL: URL
    let skipBuild: Int?

    init(kind: Kind, appStoreURL: URL, skipBuild: Int? = nil) {
        self.kind = kind
        self.appStoreURL = appStoreURL
        self.skipBuild = skipBuild
    }

    var canSkip: Bool { skipBuild != nil }

    var title: String {
        switch kind {
        case .need:
            return NSLocalizedString("Обновление обязательно", comment: "Need update alert title")
        case .force:
            return NSLocalizedString("Рекомендуется обновление", comment: "Force update alert title")
        case .soft:
            return NSLocalizedString("Доступно обновление", comment: "Soft update alert title")
        }
    }

    var message: String {
        switch kind {
        case .need:
            return NSLocalizedString("Для продолжения работы необходимо обновить приложение до последней версии.", comment: "Need update alert message")
        case .force:
            return NSLocalizedString("Эта версия приложения устарела. Некоторые функции могут работать некорректно.", comment: "Force update alert message")
        case .soft:
            return NSLocalizedString("Вышла новая версия приложения с улучшениями и исправлениями.", comment: "Soft update alert message")
        }
    }
}

final class AppUpdateChecker: ObservableObject {
    @AppStorage("appIsBlocked") private var isAppBlocked: Bool = false
    @AppStorage("lastCheckedAppBuild") private var lastCheckedAppBuild: Int = 0

    @Published private(set) var needUpdateNotice: AppUpdateNotice?
    @Published private(set) var softUpdateNotice: AppUpdateNotice?
    @Published private(set) var forceUpdateNotice: AppUpdateNotice?

    private let session: URLSession
    private var didStartCheck = false

    init(session: URLSession = .shared) {
        self.session = session
    }

    func checkForUpdatesIfNeeded() {
        guard !didStartCheck else { return }
        didStartCheck = true

        Task { await fetchRemoteConfig() }
    }

    func dismissSoftUpdateIfNeeded(skipBuild: Int? = nil) {
        if let skipBuild {
            lastCheckedAppBuild = skipBuild
        }
        softUpdateNotice = nil
    }

    func openAppStore(link overrideURL: URL? = nil) {
        guard let url = overrideURL
            ?? needUpdateNotice?.appStoreURL
            ?? forceUpdateNotice?.appStoreURL
            ?? softUpdateNotice?.appStoreURL else {
            return
        }

        DispatchQueue.main.async {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
        }
    }

    private func fetchRemoteConfig() async {
        guard
            let buildType = AppConfig.APP_BUILD.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed),
            let url = URL(string: "https://static.yobble.org/config/ios/\(buildType).json")
        else {
            log("Unable to build remote config URL")
            return
        }

        do {
            let (data, response) = try await session.data(from: url)
            guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
                log("Unexpected response when fetching remote config")
                return
            }

            let decoder = JSONDecoder()
            let remoteConfig = try decoder.decode(RemoteBuildConfiguration.self, from: data)
            await MainActor.run {
                self.apply(remoteConfig)
            }
        } catch {
            log("Failed to fetch remote config: \(error)")
        }
    }

    @MainActor
    private func apply(_ config: RemoteBuildConfiguration) {
        guard let buildNumber = currentBuildNumber() else {
            log("Unable to read current build number")
            return
        }

        needUpdateNotice = nil
        forceUpdateNotice = nil
        softUpdateNotice = nil

        guard let appStoreURL = config.appStoreURL else {
            log("Config missing App Store URL")
            return
        }

//        print("buildNumber", buildNumber)
//        print("config", config.notSupportedBuild, config.minSupportedBuild, config.recommendedBuild)

        let requiresNeedUpdate = buildNumber <= config.notSupportedBuild
        if requiresNeedUpdate {
            isAppBlocked = true
            needUpdateNotice = AppUpdateNotice(kind: .need, appStoreURL: appStoreURL)
            return
        } else {
            isAppBlocked = false
        }

        let requiresForcedUpdate = buildNumber < config.minSupportedBuild
        if requiresForcedUpdate {
            softUpdateNotice = AppUpdateNotice(kind: .force, appStoreURL: appStoreURL)
            return
        }

        if buildNumber < config.recommendedBuild && config.recommendedBuild != lastCheckedAppBuild {
//            lastCheckedAppBuild = config.recommendedBuild
            softUpdateNotice = AppUpdateNotice(
                kind: .soft,
                appStoreURL: appStoreURL,
                skipBuild: config.recommendedBuild
            )
            return
        }
    }

    private func currentBuildNumber() -> Int? {
        guard let rawValue = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else {
            return nil
        }
        return Int(rawValue)
    }

    private func log(_ message: String) {
        if AppConfig.DEBUG {
            print("[AppUpdateChecker]", message)
        }
    }
}

private struct RemoteBuildConfiguration: Decodable {
    let schemaVersion: Int
    let notSupportedBuild: Int
    let minSupportedBuild: Int
    let recommendedBuild: Int
    let appStoreURL: URL?

    enum CodingKeys: String, CodingKey {
        case schemaVersion = "schema_version"
        case notSupportedBuild = "not_supported_build"
        case minSupportedBuild = "min_supported_build"
        case recommendedBuild = "recommended_build"
        case appStoreURL = "appstore_url"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        schemaVersion = try container.decodeIfPresent(Int.self, forKey: .schemaVersion) ?? 1
        notSupportedBuild = try container.decode(Int.self, forKey: .notSupportedBuild)
        minSupportedBuild = try container.decode(Int.self, forKey: .minSupportedBuild)
        recommendedBuild = try container.decode(Int.self, forKey: .recommendedBuild)
        if let urlString = try container.decodeIfPresent(String.self, forKey: .appStoreURL) {
            appStoreURL = URL(string: urlString)
        } else {
            appStoreURL = nil
        }
    }
}
