// PulseUpdates Shared Types
// Common models and types used across the module

import Foundation

// MARK: - Configuration

public struct PulseUpdatesConfig {
    public let enabled: Bool
    public let updateUrl: String
    public let runtimeVersion: String
    public let checkOnLaunch: String
    public let launchWaitMs: Int
    public let channel: String?
    public let signingKeyId: String?
    public let signingPublicKey: String?
    /// When true, remote updates MUST carry a valid Ed25519 signature and the
    /// app MUST be configured with signingKeyId/signingPublicKey, otherwise the
    /// update is refused (fail-closed). Defaults to true in release builds.
    public let requireSignature: Bool
    public let scopeKey: String

    public init(
        enabled: Bool,
        updateUrl: String,
        runtimeVersion: String,
        checkOnLaunch: String = "ALWAYS",
        launchWaitMs: Int = 0,
        channel: String? = nil,
        signingKeyId: String? = nil,
        signingPublicKey: String? = nil,
        requireSignature: Bool = PulseUpdatesConfig.defaultRequireSignature
    ) {
        self.enabled = enabled
        self.updateUrl = updateUrl
        self.runtimeVersion = runtimeVersion
        self.checkOnLaunch = checkOnLaunch
        self.launchWaitMs = launchWaitMs
        self.channel = channel
        self.signingKeyId = signingKeyId
        self.signingPublicKey = signingPublicKey
        self.requireSignature = requireSignature
        // Use update URL host as scope key (like Expo)
        self.scopeKey = URL(string: updateUrl)?.host ?? "default"
    }

    /// Default for requireSignature: fail-closed in release, fail-open only in DEBUG.
    /// Callers that don't pass requireSignature (e.g. the JS-driven configure bridge)
    /// inherit this release-secure default automatically.
    public static var defaultRequireSignature: Bool {
        #if DEBUG
        return false
        #else
        return true
        #endif
    }
}

// MARK: - Manifest Models

public struct PulseManifestModel: Codable {
    public let updateId: String
    public let runtimeVersion: String
    public let createdAt: String
    public let bundle: PulseBundleModel
    public let assets: [PulseAssetModel]
    public let metadata: [String: String]?
    public let signature: PulseSignatureModel?

    public var commitTime: Date? {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        return formatter.date(from: createdAt) ?? ISO8601DateFormatter().date(from: createdAt)
    }
}

public struct PulseBundleModel: Codable {
    public let hash: String
    public let url: String
    public let contentType: String
}

public struct PulseAssetModel: Codable {
    public let key: String?
    public let hash: String
    public let url: String
    public let contentType: String
    // Expo-compatible fields for embedded asset lookup
    public let name: String?
    public let type: String?
    public let scale: Double?
    public let nsBundleDir: String?
    public let nsBundleFilename: String?

    public init(
        key: String?,
        hash: String,
        url: String,
        contentType: String,
        name: String? = nil,
        type: String? = nil,
        scale: Double? = nil,
        nsBundleDir: String? = nil,
        nsBundleFilename: String? = nil
    ) {
        self.key = key
        self.hash = hash
        self.url = url
        self.contentType = contentType
        self.name = name
        self.type = type
        self.scale = scale
        self.nsBundleDir = nsBundleDir
        self.nsBundleFilename = nsBundleFilename
    }
}

public struct PulseSignatureModel: Codable {
    public let alg: String
    public let keyId: String
    public let sig: String
}

// MARK: - Update Status

public enum PulseUpdateStatus: String, Codable {
    case pending = "pending"
    case downloading = "downloading"
    case ready = "ready"
    case embedded = "embedded"
    case development = "development"
    case failed = "failed"

    // Legacy compatibility
    static func fromLegacy(_ value: String) -> PulseUpdateStatus {
        switch value.uppercased() {
        case "DOWNLOADING": return .downloading
        case "READY": return .ready
        case "FAILED": return .failed
        case "EMBEDDED": return .embedded
        case "DEVELOPMENT": return .development
        default: return .pending
        }
    }

    // Database string conversion
    init?(dbValue: String?) {
        guard let value = dbValue else { return nil }
        self.init(rawValue: value)
    }
}

// MARK: - Update Info (for SelectionPolicy)

public struct PulseUpdateInfo {
    public let updateId: String
    public let runtimeVersion: String
    public let commitTime: Date
    public let status: PulseUpdateStatus
    public let successfulLaunchCount: Int
    public let failedLaunchCount: Int
    public let isEmbedded: Bool
    public let bundleHash: String?
    public let scopeKey: String?

    public init(
        updateId: String,
        runtimeVersion: String,
        commitTime: Date,
        status: PulseUpdateStatus,
        successfulLaunchCount: Int = 0,
        failedLaunchCount: Int = 0,
        isEmbedded: Bool = false,
        bundleHash: String? = nil,
        scopeKey: String? = nil
    ) {
        self.updateId = updateId
        self.runtimeVersion = runtimeVersion
        self.commitTime = commitTime
        self.status = status
        self.successfulLaunchCount = successfulLaunchCount
        self.failedLaunchCount = failedLaunchCount
        self.isEmbedded = isEmbedded
        self.bundleHash = bundleHash
        self.scopeKey = scopeKey
    }
}

// MARK: - Remote Load Status

public enum PulseRemoteLoadStatus: String {
    case idle = "Idle"
    case loading = "Loading"
    case newUpdateLoaded = "NewUpdateLoaded"
    case updateUnavailable = "UpdateUnavailable"
}

// MARK: - Check Result

public struct PulseCheckResult {
    public let isAvailable: Bool
    public let manifest: PulseManifestModel?
    public let isRollback: Bool
    public let failedReason: String?

    public init(isAvailable: Bool, manifest: PulseManifestModel? = nil, isRollback: Bool = false, failedReason: String? = nil) {
        self.isAvailable = isAvailable
        self.manifest = manifest
        self.isRollback = isRollback
        self.failedReason = failedReason
    }
}

// MARK: - Fetch Result

public struct PulseFetchResult {
    public let isNew: Bool
    public let manifest: PulseManifestModel?

    public init(isNew: Bool, manifest: PulseManifestModel? = nil) {
        self.isNew = isNew
        self.manifest = manifest
    }
}

// MARK: - Errors

public enum PulseUpdatesError: Error, LocalizedError {
    case notConfigured
    case notEnabled
    case networkError(underlying: Error)
    case invalidManifest(reason: String)
    case signatureVerificationFailed
    case signatureRequiredButMissing
    case hashMismatch(expected: String, actual: String)
    case downloadFailed(underlying: Error)
    case noUpdateAvailable
    case updateNotReady
    case databaseError(underlying: Error)
    case launchFailed(reason: String)

    public var errorDescription: String? {
        switch self {
        case .notConfigured:
            return "PulseUpdates is not configured"
        case .notEnabled:
            return "PulseUpdates is not enabled"
        case .networkError(let error):
            return "Network error: \(error.localizedDescription)"
        case .invalidManifest(let reason):
            return "Invalid manifest: \(reason)"
        case .signatureVerificationFailed:
            return "Signature verification failed"
        case .signatureRequiredButMissing:
            return "Signature required but missing: the SDK is configured to require signed updates but no signing key is configured or the manifest is unsigned"
        case .hashMismatch(let expected, let actual):
            return "Hash mismatch: expected \(expected), got \(actual)"
        case .downloadFailed(let error):
            return "Download failed: \(error.localizedDescription)"
        case .noUpdateAvailable:
            return "No update available"
        case .updateNotReady:
            return "Update is not ready to launch"
        case .databaseError(let error):
            return "Database error: \(error.localizedDescription)"
        case .launchFailed(let reason):
            return "Launch failed: \(reason)"
        }
    }
}

// MARK: - Event Types

public enum PulseUpdatesEventType: String {
    case noUpdateAvailable = "noUpdateAvailable"
    case updateAvailable = "updateAvailable"
    case downloadStart = "downloadStart"
    case downloadProgress = "downloadProgress"
    case downloadComplete = "downloadComplete"
    case error = "error"
}

// MARK: - Logging

/// Debug logging - only prints in DEBUG builds or when PULSE_DEBUG env var is set
public func pulseLog(_ message: String) {
    #if DEBUG
    print("[PulseUpdates] \(message)")
    #else
    if ProcessInfo.processInfo.environment["PULSE_DEBUG"] != nil {
        print("[PulseUpdates] \(message)")
    }
    #endif
}

public func pulseLogWarn(_ message: String) {
    #if DEBUG
    print("[PulseUpdates] WARN: \(message)")
    #else
    if ProcessInfo.processInfo.environment["PULSE_DEBUG"] != nil {
        print("[PulseUpdates] WARN: \(message)")
    }
    #endif
}

public func pulseLogError(_ message: String) {
    // Always log errors
    print("[PulseUpdates] ERROR: \(message)")
}
