import ComposableArchitecture
import Foundation
import Utilities

public enum BackendEnvironment: String, CaseIterable, Codable, Equatable, Sendable {
    case production
    case staging

    var persistedValue: String { rawValue }

    public var displayName: String {
        switch self {
        case .production: "Production"
        case .staging: "Staging"
        }
    }
}

public struct BackendEnvironmentConfig: Equatable, Sendable {
    public let apiEndpointHost: String
    public let webEndpointHost: String
    public let statusEndpointPath: String
    public let clerkPublishableKey: String
    public let scsdkClientId: String
    public let segmentProxyEndpoint: String
    public let statsigEnvironment: String
    public let hcaptchaKey: String
    public let hcaptchaDomain: String
    public let brazeApiKey: String
    public let orpheusEndpointHost: String
}

public extension BackendEnvironmentConfig {
    static func configuration(for environment: BackendEnvironment) -> BackendEnvironmentConfig {
        // All builds now load from environment-specific plist files
        // Production builds: Only have Production.plist (Staging.plist excluded)
        // Staff builds: Have both Production.plist and Staging.plist for runtime switching
        return loadFromEnvironmentPlist(environment)
    }

    private static func loadFromEnvironmentPlist(_ environment: BackendEnvironment) -> BackendEnvironmentConfig {
        let plistName = switch environment {
        case .production: "Production"
        case .staging: "Staging"
        }

        // Load environment-specific values from plist file
        guard let url = Bundle.main.url(forResource: plistName, withExtension: "plist"),
              let data = try? Data(contentsOf: url),
              let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any] else {
            fatalError("Failed to load \(plistName).plist from main bundle")
        }

        func getPlistString(_ key: String) -> String {
            guard let value = plist[key] as? String else {
                fatalError("Missing required key '\(key)' in \(plistName).plist")
            }
            return value
        }

        func getBundleString(_ key: String) -> String {
            let info = Bundle.main.infoDictionary
            return (info?[key] as? String) ?? ""
        }

        func getConfigValue(_ key: String) -> String {
            // If bundle string is not empty, use it, otherwise use plist string
            // This allows for overrides at build level
            let bundleString = getBundleString(key).trimmingCharacters(in: .whitespacesAndNewlines)
            if !bundleString.isEmpty {
                return bundleString
            }
            return getPlistString(key)
        }

        return BackendEnvironmentConfig(
            apiEndpointHost: getConfigValue("API_ENDPOINT"),
            webEndpointHost: getConfigValue("WEB_ENDPOINT"),
            statusEndpointPath: getConfigValue("STATUS_ENDPOINT"),
            clerkPublishableKey: getConfigValue("CLERK_PUBLISHABLE_KEY"),
            scsdkClientId: getConfigValue("SCSDKClientId"),
            segmentProxyEndpoint: getConfigValue("SEGMENT_PROXY_ENDPOINT"),
            statsigEnvironment: getConfigValue("STATSIG_ENVIRONMENT"),
            hcaptchaKey: getConfigValue("HCAPTCHA_KEY"),
            hcaptchaDomain: getConfigValue("HCAPTCHA_DOMAIN"),
            brazeApiKey: getConfigValue("BRAZE_API_KEY"),
            orpheusEndpointHost: getConfigValue("ORPHEUS_ENDPOINT")
        )
    }
}

public enum BackendEnvironmentProvider {
    private static let overrideKey = String.selectedBackendEnvironment

    public static func isStaffBuild() -> Bool {
        Bundle.main.bundleIdentifier?.contains("staff") ?? false
    }

    public static func defaultEnvironment() -> BackendEnvironment {
        guard let info = Bundle.main.infoDictionary,
              let defaultEnv = info["DEFAULT_BACKEND_ENVIRONMENT"] as? String,
              let environment = BackendEnvironment(rawValue: defaultEnv.lowercased()) else {
            return .production
        }
        return environment
    }

    public static func currentEnvironment() -> BackendEnvironment {
        // Only staff builds can override the environment
        guard isStaffBuild() else { return defaultEnvironment() }

        let storedValue = UserDefaults.standard.string(forKey: overrideKey)
        return storedValue.flatMap(BackendEnvironment.init(rawValue:)) ?? defaultEnvironment()
    }

    public static func currentConfiguration() -> BackendEnvironmentConfig {
        BackendEnvironmentConfig.configuration(for: currentEnvironment())
    }
}

@DependencyClient
public struct BackendEnvironmentClient: Sendable {
    public var current: @Sendable () -> BackendEnvironment = { .production }
    public var configuration: @Sendable () -> BackendEnvironmentConfig = { .configuration(for: .production) }
    public var setEnvironment: @Sendable (_ environment: BackendEnvironment) async -> Void
    public var resetOverride: @Sendable () async -> Void
    public var isStaffBuild: @Sendable () -> Bool = { false }
}

extension BackendEnvironmentClient: DependencyKey {
    public static let liveValue: Self = {
        @Shared(.appStorage(.selectedBackendEnvironment)) var selectedEnvironmentRawValue: String?

        let isStaff = BackendEnvironmentProvider.isStaffBuild()

        func resolvedEnvironment() -> BackendEnvironment {
            if let rawValue = selectedEnvironmentRawValue,
               let environment = BackendEnvironment(rawValue: rawValue) {
                return environment
            }
            return BackendEnvironmentProvider.currentEnvironment()
        }

        func store(environment: BackendEnvironment) {
            guard isStaff else { return }
            $selectedEnvironmentRawValue.withLock { $0 = environment.persistedValue }
        }

        func clearStoredEnvironment() {
            $selectedEnvironmentRawValue.withLock { $0 = nil }
        }

        return Self(
            current: {
                resolvedEnvironment()
            },
            configuration: {
                let environment = resolvedEnvironment()
                return BackendEnvironmentConfig.configuration(for: environment)
            },
            setEnvironment: { environment in
                store(environment: environment)
            },
            resetOverride: {
                clearStoredEnvironment()
            },
            isStaffBuild: {
                isStaff
            }
        )
    }()

    public static let previewValue = Self.noop
    public static let testValue = Self.noop
}

public extension BackendEnvironmentClient {
    static let noop = Self(
        current: { .production },
        configuration: { BackendEnvironmentConfig.configuration(for: .production) },
        setEnvironment: { _ in },
        resetOverride: {},
        isStaffBuild: { false }
    )
}
