import Foundation

struct HistoryItem {

    let outgoingDate: Date

    let outgoingMessage: Message

    let incomingDate: Date?

    let incomingMessage: Message?

    let response: ClientState?

    init(sent message: Message, date: Date) {
        self.outgoingDate = date
        self.outgoingMessage = message
        self.incomingDate = nil
        self.incomingMessage = nil
        self.response = nil
    }

    func didReceive(response: ClientState, date: Date?, message: Message?) -> HistoryItem {
        .init(sent: self, response: response, date: date, message: message)
    }

    func invalidated() -> HistoryItem {
        didReceive(response: .responseRejected(.invalidAuthentication), date: incomingDate, message: incomingMessage)
    }

    func notAuthenticated() -> HistoryItem {
        didReceive(response: .responseRejected(.missingKey), date: incomingDate, message: incomingMessage)
    }

    private init(sent: HistoryItem, response: ClientState, date: Date?, message: Message?) {
        self.outgoingDate = sent.outgoingDate
        self.outgoingMessage = sent.outgoingMessage
        self.incomingDate = date
        self.incomingMessage = message
        self.response = response
    }

    // MARK: Statistics

    var roundTripTime: TimeInterval? {
        incomingDate?.timeIntervalSince(outgoingDate)
    }

    var deviceTime: Date? {
        guard let timestamp = incomingMessage?.content.time else {
            return nil
        }
        return Date(timestamp: timestamp)
    }

    var requestLatency: TimeInterval? {
        deviceTime?.timeIntervalSince(outgoingDate)
    }

    var responseLatency: TimeInterval? {
        guard let deviceTime = deviceTime else {
            return nil
        }
        return incomingDate?.timeIntervalSince(deviceTime)
    }

    var clockOffset: Int? {
        guard let interval = roundTripTime, let deviceTime = deviceTime else {
            return nil
        }
        let estimatedArrival = outgoingDate.advanced(by: interval / 2)
        return Int(deviceTime.timeIntervalSince(estimatedArrival))
    }

    // MARK: Coding

    static func testEncoding() {

    }

    var encoded: Data {
        var result = outgoingDate.encoded + outgoingMessage.encoded
        if let date = incomingDate {
            result += Data([1]) + date.encoded
        } else {
            result += Data([0])
        }
        if let message = incomingMessage {
            result += Data([1]) + message.encoded
        } else {
            result += Data([0])
        }
        result += response?.encoded ?? Data([0])
        return result
    }

    init?(decodeFrom data: Data, index: inout Int) {
        guard let outgoingDate = Date(decodeFrom: data, index: &index) else {
            return nil
        }
        self.outgoingDate = outgoingDate

        guard let outgoingMessage = Message(decodeFrom: data, index: &index) else {
            return nil
        }
        self.outgoingMessage = outgoingMessage

        if data[index] > 0 {
            index += 1
            guard let incomingDate = Date(decodeFrom: data, index: &index) else {
                return nil
            }
            self.incomingDate = incomingDate
        } else {
            self.incomingDate = nil
            index += 1
        }

        if data[index] > 0 {
            index += 1
            guard let incomingMessage = Message(decodeFrom: data, index: &index) else {
                return nil
            }
            self.incomingMessage = incomingMessage
        } else {
            self.incomingMessage = nil
            index += 1
        }
        guard index < data.count else {
            return nil
        }
        self.response = ClientState(code: data[index])
        index += 1
    }
}

private extension Date {

    static var encodedSize: Int {
        MemoryLayout<Double>.size
    }

    var encoded: Data {
        .init(from: timeIntervalSince1970)
    }

    init?(decodeFrom data: Data, index: inout Int) {
        guard index + Date.encodedSize <= data.count else {
            return nil
        }
        self.init(timeIntervalSince1970: data.advanced(by: index).convert(into: .zero))
        index += Date.encodedSize
    }
}

extension HistoryItem: Identifiable {

    var id: UInt32 {
        outgoingDate.timestamp
    }
}

extension HistoryItem: Comparable {

    static func < (lhs: HistoryItem, rhs: HistoryItem) -> Bool {
        lhs.outgoingDate < rhs.outgoingDate
    }
}
