import Foundation

/// Represents events from a Server-Sent Events stream.
public enum SseEvent: Equatable {
    /// Connection successfully established.
    case connected

    /// Connection closed (stream ended or timeout).
    case disconnected

    /// Message received from SSE stream.
    /// - Parameters:
    ///   - data: The accumulated data from all "data:" lines, joined with newlines
    ///   - id: The event ID from "id:" field, used for reconnection
    case message(data: String, id: String?)

    /// Error occurred during connection or parsing.
    case error(Error)

    // MARK: - Equatable

    public static func == (lhs: SseEvent, rhs: SseEvent) -> Bool {
        switch (lhs, rhs) {
        case (.connected, .connected), (.disconnected, .disconnected):
            return true
        case (.message(let lhsData, let lhsId), .message(let rhsData, let rhsId)):
            return lhsData == rhsData && lhsId == rhsId
        case (.error(let lhsError), .error(let rhsError)):
            return lhsError.localizedDescription == rhsError.localizedDescription
        default:
            return false
        }
    }
}

/// Authentication token for SSE connections with optional expiry.
public struct SseAuthToken: Equatable {
    /// The complete Authorization header value (e.g., "Bearer token123")
    public let authorizationHeader: String

    /// Token expiry timestamp in milliseconds, or nil if no expiry
    public let expiresAtMs: Int64?

    public init(
        authorizationHeader: String,
        expiresAtMs: Int64? = nil
    ) {
        self.authorizationHeader = authorizationHeader
        self.expiresAtMs = expiresAtMs
    }
}

/// Configuration for establishing an SSE connection.
public struct SseConnectionConfig: Equatable {
    /// The SSE endpoint URL
    public let url: String

    /// Query parameters to append to the URL
    public let queryParameters: [String: String]

    /// Initial retry delay in milliseconds
    public let initialRetryDelayMs: Int64

    /// Maximum retry delay in milliseconds
    public let maxRetryDelayMs: Int64

    /// Multiplier for exponential backoff
    public let retryBackoffMultiplier: Double

    /// Maximum retry attempts before capping backoff delay
    public let maxRetryAttemptForBackoff: Int

    /// Maximum number of retry attempts before giving up
    public let maxRetries: Int

    /// Maximum size in bytes for incomplete SSE events (prevents memory leaks)
    public let maxEventSizeBytes: Int

    public init(
        url: String,
        queryParameters: [String: String] = [:],
        initialRetryDelayMs: Int64 = 1000,
        maxRetryDelayMs: Int64 = 30000,
        retryBackoffMultiplier: Double = 2.0,
        maxRetryAttemptForBackoff: Int = 10,
        maxRetries: Int = 10,
        maxEventSizeBytes: Int = 1048576 // 1MB
    ) {
        self.url = url
        self.queryParameters = queryParameters
        self.initialRetryDelayMs = initialRetryDelayMs
        self.maxRetryDelayMs = maxRetryDelayMs
        self.retryBackoffMultiplier = retryBackoffMultiplier
        self.maxRetryAttemptForBackoff = maxRetryAttemptForBackoff
        self.maxRetries = maxRetries
        self.maxEventSizeBytes = maxEventSizeBytes
    }
}

/// Parses Server-Sent Events lines according to the WHATWG SSE specification.
/// https://html.spec.whatwg.org/multipage/server-sent-events.html
public struct SseLineParser {
    private let ID_LINE_PREFIX = "id:"
    private let DATA_LINE_PREFIX = "data:"
    private let EVENT_LINE_PREFIX = "event:"
    private let RETRY_LINE_PREFIX = "retry:"
    private let COMMENT_PREFIX = ":"

    public init() {}

    /// Parses a single SSE line according to the specification.
    public func parseLine(_ rawLine: String) -> ParsedLine {
        if rawLine.hasPrefix(ID_LINE_PREFIX) {
            let id = String(rawLine.dropFirst(ID_LINE_PREFIX.count)).trimmingCharacters(in: .whitespaces)
            return .id(id)
        } else if rawLine.hasPrefix(DATA_LINE_PREFIX) {
            let data = String(rawLine.dropFirst(DATA_LINE_PREFIX.count)).trimmingCharacters(in: .whitespaces)
            return .data(data)
        } else if rawLine.hasPrefix(EVENT_LINE_PREFIX) {
            let eventType = String(rawLine.dropFirst(EVENT_LINE_PREFIX.count)).trimmingCharacters(in: .whitespaces)
            return .event(eventType)
        } else if rawLine.hasPrefix(RETRY_LINE_PREFIX) {
            let retryValue = String(rawLine.dropFirst(RETRY_LINE_PREFIX.count)).trimmingCharacters(in: .whitespaces)
            let retryMs = Int64(retryValue)
            return .retry(retryMs)
        } else if rawLine.hasPrefix(COMMENT_PREFIX) {
            return .comment(rawLine)
        } else if rawLine.trimmingCharacters(in: .whitespaces).isEmpty {
            return .endOfEvent(rawLine)
        } else {
            return .other(rawLine)
        }
    }

    public enum ParsedLine: Equatable {
        /// Event ID for reconnection via lastEventId param.
        case id(String)

        /// Event data. Multiple data lines are joined with newlines per SSE spec.
        case data(String)

        /// Event type.
        case event(String)

        /// Reconnection time in milliseconds.
        case retry(Int64?)

        /// Comment line starting with ':'. Ignored per SSE spec.
        case comment(String)

        /// Blank line indicating end of event. Triggers event dispatch per SSE spec.
        case endOfEvent(String)

        /// Unrecognized line format. Ignored per SSE spec.
        case other(String)
    }
}
