import Foundation


struct HistoryItem {
    
    
    /// The sent/received date (local time, not including compensation offset)
    let requestDate: Date

    let request: Message.Content
    
    let usedLocalConnection: Bool
    
    let response: ClientState?
    
    let responseMessage: Message.Content?
    
    let responseDate: Date?

    init(sent message: Message.Content, date: Date, local: Bool) {
        self.requestDate = date
        self.request = message
        self.responseMessage = nil
        self.response = nil
        self.responseDate = nil
        self.usedLocalConnection = local
    }

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

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

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

    private init(sent: HistoryItem, response: ClientState, date: Date?, message: Message.Content?) {
        self.requestDate = sent.requestDate
        self.request = sent.request
        self.responseDate = date
        self.responseMessage = message
        self.response = response
        self.usedLocalConnection = sent.usedLocalConnection
    }

    // MARK: Statistics

    var roundTripTime: TimeInterval? {
        responseDate?.timeIntervalSince(requestDate)
    }

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

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

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

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

}

extension HistoryItem: Codable {
    
    enum CodingKeys: Int, CodingKey {
        case requestDate = 1
        case request = 2
        case usedLocalConnection = 3
        case response = 4
        case responseMessage = 5
        case responseDate = 6
    }
}

extension ClientState: Codable {
    
    init(from decoder: Decoder) throws {
        let code = try decoder.singleValueContainer().decode(UInt8.self)
        self.init(code: code)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(code)
    }
}

extension HistoryItem: Identifiable {

    var id: UInt32 {
        requestDate.timestamp
    }
}

extension HistoryItem: Comparable {

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

extension HistoryItem {

    static var mock: HistoryItem {
        let content = Message.Content(time: Date.now.timestamp, id: 123)
        let content2 = Message.Content(time: (Date.now + 1).timestamp, id: 124)
        return .init(sent: content, date: .now, local: false)
            .didReceive(response: .openSesame, date: .now + 2, message: content2)
    }
}
