//
//  TemporaryScheduleOverrideHistoryTests.swift
//  LoopKitTests
//
//  Created by Michael Pangburn on 3/25/19.
//  Copyright © 2019 LoopKit Authors. All rights reserved.
//

import XCTest
@testable import LoopKit


final class TemporaryScheduleOverrideHistoryTests: XCTestCase {
    // Midnight of an arbitrary date
    let referenceDate = Calendar.current.startOfDay(for: Date(timeIntervalSinceReferenceDate: .hours(100_000)))

    let basalRateSchedule = BasalRateSchedule(
        dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(6), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.0)
        ],
        timeZone: Calendar.current.timeZone
    )!

    let history = TemporaryScheduleOverrideHistory()

    private func recordOverride(
        beginningAt offset: TimeInterval,
        duration: TemporaryScheduleOverride.Duration,
        insulinNeedsScaleFactor scaleFactor: Double,
        recordedAt enableDateOffset: TimeInterval? = nil
    ) {
        let settings = TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter, targetRange: nil, insulinNeedsScaleFactor: scaleFactor)
        let override = TemporaryScheduleOverride(context: .custom, settings: settings, startDate: referenceDate + offset, duration: duration, enactTrigger: .local, syncIdentifier: UUID())
        let enableDate: Date
        if let enableDateOffset = enableDateOffset {
            enableDate = referenceDate + enableDateOffset
        } else {
            enableDate = override.startDate
        }
        history.recordOverride(override, at: enableDate)
    }

    private func recordOverrideDisable(at offset: TimeInterval) {
        history.recordOverride(nil, at: referenceDate + offset)
    }

    private func historyResolves(to expected: BasalRateSchedule, referenceDateOffset: TimeInterval = 0) -> Bool {
        let referenceDate = self.referenceDate + referenceDateOffset
        let actual = history.resolvingRecentBasalSchedule(basalRateSchedule, relativeTo: referenceDate)
        return actual.equals(expected, accuracy: 1e-6)
    }

    override func setUp() {
        history.wipeHistory()
    }

    func testEmptyHistory() {
        XCTAssert(historyResolves(to: basalRateSchedule))
    }

    func testSingleOverrideNaturalEnd() {
        recordOverride(beginningAt: .hours(2), duration: .finite(.hours(3)), insulinNeedsScaleFactor: 1.5)
        let expected = BasalRateSchedule(dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(2), value: 1.8),
            RepeatingScheduleValue(startTime: .hours(5), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(6), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.0)
        ])!

        XCTAssert(historyResolves(to: expected, referenceDateOffset: .hours(3)))
    }

    func testSingleOverrideEarlyEnd() {
        recordOverride(beginningAt: .hours(2), duration: .finite(.hours(3)), insulinNeedsScaleFactor: 1.5)
        recordOverrideDisable(at: .hours(3))
        let expected = BasalRateSchedule(dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(2), value: 1.8),
            RepeatingScheduleValue(startTime: .hours(3), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(6), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.0)
        ])!

        XCTAssert(historyResolves(to: expected, referenceDateOffset: .hours(3)))
    }

    func testSingleIndefiniteOverrideEarlyEnd() {
        recordOverride(beginningAt: .hours(2), duration: .indefinite, insulinNeedsScaleFactor: 1.5)
        recordOverrideDisable(at: .hours(3))
        let expected = BasalRateSchedule(dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(2), value: 1.8),
            RepeatingScheduleValue(startTime: .hours(3), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(6), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.0)
        ])!

        XCTAssert(historyResolves(to: expected, referenceDateOffset: .hours(3)))
    }

    func testTwoOverrides() {
        recordOverride(beginningAt: .hours(2), duration: .finite(.hours(3)), insulinNeedsScaleFactor: 1.5)
        recordOverride(beginningAt: .hours(6), duration: .finite(.hours(4)), insulinNeedsScaleFactor: 2.0)
        let expected = BasalRateSchedule(dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(2), value: 1.8),
            RepeatingScheduleValue(startTime: .hours(5), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(6), value: 2.8),
            RepeatingScheduleValue(startTime: .hours(10), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.0)
        ])!

        XCTAssert(historyResolves(to: expected, referenceDateOffset: .hours(10)))
    }

    func testThreeOverrides() {
        recordOverride(beginningAt: .hours(5), duration: .finite(.hours(3)), insulinNeedsScaleFactor: 1.5)
        recordOverrideDisable(at: .hours(6))
        recordOverride(beginningAt: .hours(10), duration: .finite(.hours(1)), insulinNeedsScaleFactor: 2.0)
        recordOverride(beginningAt: .hours(12), duration: .finite(.hours(2)), insulinNeedsScaleFactor: 1.5)
        recordOverrideDisable(at: .hours(13))
        let expected = BasalRateSchedule(dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(5), value: 1.8),
            RepeatingScheduleValue(startTime: .hours(6), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(10), value: 2.8),
            RepeatingScheduleValue(startTime: .hours(11), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(12), value: 2.1),
            RepeatingScheduleValue(startTime: .hours(13), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.0)
        ])!

        XCTAssert(historyResolves(to: expected, referenceDateOffset: .hours(13)))
    }

    func testOldOverrideRemoval() {
        recordOverride(beginningAt: .hours(-1000), duration: .finite(.hours(1)), insulinNeedsScaleFactor: 2.0)
        recordOverride(beginningAt: .hours(2), duration: .finite(.hours(3)), insulinNeedsScaleFactor: 1.5)
        let expected = BasalRateSchedule(dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(2), value: 1.8),
            RepeatingScheduleValue(startTime: .hours(5), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(6), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.0)
        ])!

        XCTAssert(historyResolves(to: expected))
    }

    func testActiveIndefiniteOverride() {
        recordOverride(beginningAt: .hours(2), duration: .finite(.hours(3)), insulinNeedsScaleFactor: 1.5)
        recordOverride(beginningAt: .hours(6), duration: .indefinite, insulinNeedsScaleFactor: 2.0)
        let expected = BasalRateSchedule(dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(2), value: 1.8),
            RepeatingScheduleValue(startTime: .hours(5), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(6), value: 2.8),
            RepeatingScheduleValue(startTime: .hours(10), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.0)
        ])!

        XCTAssert(historyResolves(to: expected))
    }

    func testEditActiveOverride() {
        recordOverride(beginningAt: .hours(1), duration: .finite(.hours(1)), insulinNeedsScaleFactor: 1.5, recordedAt: .hours(0))
        recordOverride(beginningAt: .hours(1), duration: .finite(.hours(1)), insulinNeedsScaleFactor: 2.0, recordedAt: .hours(0.5))
        let expected = BasalRateSchedule(dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(1), value: 2.4),
            RepeatingScheduleValue(startTime: .hours(2), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(6), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.0)
        ])!

        XCTAssert(historyResolves(to: expected))
    }

    func testRemoveFutureOverride() {
        recordOverride(beginningAt: .hours(2), duration: .finite(.hours(3)), insulinNeedsScaleFactor: 1.5, recordedAt: .hours(1))
        recordOverride(beginningAt: .hours(1), duration: .finite(.hours(1)), insulinNeedsScaleFactor: 2.0)
        let expected = BasalRateSchedule(dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(1), value: 2.4),
            RepeatingScheduleValue(startTime: .hours(2), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(6), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.0)
        ])!

        XCTAssert(historyResolves(to: expected))
    }

    func testCancelFutureOverride() {
        recordOverride(beginningAt: .hours(2), duration: .finite(.hours(3)), insulinNeedsScaleFactor: 1.5, recordedAt: .hours(1))
        recordOverrideDisable(at: .hours(1.5))
        let expected = basalRateSchedule
        XCTAssert(historyResolves(to: expected))
    }

    func testMultiDayOverride() {
        recordOverride(beginningAt: .hours(2), duration: .finite(.hours(68)), insulinNeedsScaleFactor: 1.5)

        let expected = BasalRateSchedule(dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.8),
            RepeatingScheduleValue(startTime: .hours(6), value: 2.1),
            RepeatingScheduleValue(startTime: .hours(12), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(16), value: 2.1),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.5)
        ])!

        XCTAssert(historyResolves(to: expected, referenceDateOffset: .hours(26)))
    }

    func testClampedPastOverride() {
        recordOverride(beginningAt: .hours(-4), duration: .finite(.hours(8)), insulinNeedsScaleFactor: 1.5)

        let expected = BasalRateSchedule(dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.8),
            RepeatingScheduleValue(startTime: .hours(4), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(6), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.5),
        ])!

        XCTAssert(historyResolves(to: expected, referenceDateOffset: .hours(6)))
    }

    func testCancelSequence() {
        recordOverride(beginningAt: .hours(2), duration: .finite(.hours(8)), insulinNeedsScaleFactor: 1.5)
        recordOverrideDisable(at: .hours(4))
        recordOverride(beginningAt: .hours(7), duration: .finite(.hours(1)), insulinNeedsScaleFactor: 1.5)
        let expected = BasalRateSchedule(dailyItems: [
            RepeatingScheduleValue(startTime: .hours(0), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(2), value: 1.8),
            RepeatingScheduleValue(startTime: .hours(4), value: 1.2),
            RepeatingScheduleValue(startTime: .hours(6), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(7), value: 2.1),
            RepeatingScheduleValue(startTime: .hours(8), value: 1.4),
            RepeatingScheduleValue(startTime: .hours(20), value: 1.0)
        ])!

        XCTAssert(historyResolves(to: expected, referenceDateOffset: .hours(6)))
    }
    
    func testQuery() {
        var (overrides, deletedOverrides, newAnchor) = history.queryByAnchor(nil)
        
        XCTAssertEqual(0, overrides.count)
        XCTAssertEqual(0, deletedOverrides.count)
        
        recordOverride(beginningAt: .hours(2), duration: .finite(.hours(8)), insulinNeedsScaleFactor: 1.5)
        recordOverrideDisable(at: .hours(4))
        recordOverride(beginningAt: .hours(7), duration: .finite(.hours(1)), insulinNeedsScaleFactor: 1.5)
        
        (overrides, deletedOverrides, newAnchor) = history.queryByAnchor(newAnchor)
        
        XCTAssertEqual(0, deletedOverrides.count)
        XCTAssertEqual(2, overrides.count)
        
        XCTAssertEqual(TimeInterval(hours: 2), overrides[0].duration.timeInterval, accuracy: 1)
    }

    func testQueryOfDeletedOverrides() {
        var (overrides, deletedOverrides, newAnchor) = history.queryByAnchor(nil)
        
        XCTAssertEqual(0, overrides.count)
        XCTAssertEqual(0, deletedOverrides.count)
        
        recordOverride(beginningAt: .hours(2), duration: .finite(.hours(8)), insulinNeedsScaleFactor: 1.5)
        recordOverrideDisable(at: .hours(1))
        
        (overrides, deletedOverrides, newAnchor) = history.queryByAnchor(newAnchor)
        
        XCTAssertEqual(0, overrides.count)
        XCTAssertEqual(1, deletedOverrides.count)
    }

}
