//
//  BolusRecommendationTests.swift
//  LoopKitTests
//
//  Created by Darin Krauss on 5/4/20.
//  Copyright © 2020 LoopKit Authors. All rights reserved.
//

import XCTest
import HealthKit

@testable import LoopKit

class BolusRecommendationNoticeCodableTests: XCTestCase {
    func testCodableGlucoseBelowSuspendThreshold() throws {
        let glucoseValue = SimpleGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:14:16Z")!,
                                              quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 65.0))
        try assertBolusRecommendationNoticeCodable(.glucoseBelowSuspendThreshold(minGlucose: glucoseValue), encodesJSON: """
{
  "bolusRecommendationNotice" : {
    "glucoseBelowSuspendThreshold" : {
      "minGlucose" : {
        "endDate" : "2020-05-14T22:14:16Z",
        "quantity" : 65,
        "quantityUnit" : "mg/dL",
        "startDate" : "2020-05-14T22:14:16Z"
      }
    }
  }
}
"""
        )
    }

    func testCodableCurrentGlucoseBelowTarget() throws {
        let glucoseValue = SimpleGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:20:16Z")!,
                                              quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 85.0))
        try assertBolusRecommendationNoticeCodable(.currentGlucoseBelowTarget(glucose: glucoseValue), encodesJSON: """
{
  "bolusRecommendationNotice" : {
    "currentGlucoseBelowTarget" : {
      "glucose" : {
        "endDate" : "2020-05-14T22:20:16Z",
        "quantity" : 85,
        "quantityUnit" : "mg/dL",
        "startDate" : "2020-05-14T22:20:16Z"
      }
    }
  }
}
"""
        )
    }

    func testCodablePredictedGlucoseBelowTarget() throws {
        let glucoseValue = SimpleGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:38:16Z")!,
                                              quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 80.0))
        try assertBolusRecommendationNoticeCodable(.predictedGlucoseBelowTarget(minGlucose: glucoseValue), encodesJSON: """
{
  "bolusRecommendationNotice" : {
    "predictedGlucoseBelowTarget" : {
      "minGlucose" : {
        "endDate" : "2020-05-14T22:38:16Z",
        "quantity" : 80,
        "quantityUnit" : "mg/dL",
        "startDate" : "2020-05-14T22:38:16Z"
      }
    }
  }
}
"""
        )
    }

    func testCodablePredictedGlucoseInRange() throws {
        try assertBolusRecommendationNoticeCodable(.predictedGlucoseInRange, encodesJSON: """
{
  "bolusRecommendationNotice" : "predictedGlucoseInRange"
}
"""
        )
    }

    func testCodableAllGlucoseBelowTarget() throws {
        let glucoseValue = SimpleGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:38:16Z")!,
                                              quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 80.0))
        try assertBolusRecommendationNoticeCodable(.allGlucoseBelowTarget(minGlucose: glucoseValue), encodesJSON: """
{
  "bolusRecommendationNotice" : {
    "allGlucoseBelowTarget" : {
      "minGlucose" : {
        "endDate" : "2020-05-14T22:38:16Z",
        "quantity" : 80,
        "quantityUnit" : "mg/dL",
        "startDate" : "2020-05-14T22:38:16Z"
      }
    }
  }
}
"""
        )
    }

    private func assertBolusRecommendationNoticeCodable(_ original: BolusRecommendationNotice, encodesJSON string: String) throws {
        let data = try encoder.encode(TestContainer(bolusRecommendationNotice: original))
        XCTAssertEqual(String(data: data, encoding: .utf8), string)
        let decoded = try decoder.decode(TestContainer.self, from: data)
        XCTAssertEqual(decoded.bolusRecommendationNotice, original)
    }

    private let dateFormatter = ISO8601DateFormatter()

    private let encoder: JSONEncoder = {
        let encoder = JSONEncoder()
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
        encoder.dateEncodingStrategy = .iso8601
        return encoder
    }()

    private let decoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        return decoder
    }()

    private struct TestContainer: Codable, Equatable {
        let bolusRecommendationNotice: BolusRecommendationNotice
    }
}

extension BolusRecommendationNotice: Equatable {
    public static func == (lhs: BolusRecommendationNotice, rhs: BolusRecommendationNotice) -> Bool {
        switch (lhs, rhs) {
        case (.glucoseBelowSuspendThreshold(let lhsGlucoseValue), .glucoseBelowSuspendThreshold(let rhsGlucoseValue)),
             (.currentGlucoseBelowTarget(let lhsGlucoseValue), .currentGlucoseBelowTarget(let rhsGlucoseValue)),
             (.predictedGlucoseBelowTarget(let lhsGlucoseValue), .predictedGlucoseBelowTarget(let rhsGlucoseValue)),
             (.allGlucoseBelowTarget(let lhsGlucoseValue), .allGlucoseBelowTarget(let rhsGlucoseValue)):
            return lhsGlucoseValue.startDate == rhsGlucoseValue.startDate &&
                lhsGlucoseValue.endDate == rhsGlucoseValue.endDate &&
                lhsGlucoseValue.quantity == rhsGlucoseValue.quantity
        case (.predictedGlucoseInRange, .predictedGlucoseInRange):
            return true
        default:
            return false
        }
    }
}
