// Copyright © 2015 Abhishek Banthia

import CoreModelKit
import EventKit
import XCTest

@testable import Clocker

class StandardMenubarHandlerTests: XCTestCase {
    private let eventStore = EKEventStore()

    private let mumbai = ["customLabel": "Ghar",
                          "formattedAddress": "Mumbai",
                          "place_id": "ChIJwe1EZjDG5zsRaYxkjY_tpF0",
                          "timezoneID": "Asia/Calcutta",
                          "nextUpdate": "",
                          "latitude": "19.0759837",
                          "longitude": "72.8776559"]

    private func makeMockStore(with menubarMode: Int = 1) -> DataStore {
        // Wipe all timezones from UserDefaults
        let defaults = UserDefaults(suiteName: "com.abhishek.Clocker.StandardMenubarHandlerTests")!
        defaults.set(menubarMode, forKey: CLMenubarCompactMode)
        defaults.set(0, forKey: CLShowMeetingInMenubar)
        XCTAssertNotEqual(defaults, UserDefaults.standard)
        return DataStore(with: defaults)
    }

    private func saveObject(object: TimezoneData,
                            in store: DataStore,
                            at index: Int = -1)
    {
        var defaults = store.timezones()
        guard let encodedObject = NSKeyedArchiver.clocker_archive(with: object as Any) else {
            return
        }
        index == -1 ? defaults.append(encodedObject) : defaults.insert(encodedObject, at: index)
        store.setTimezones(defaults)
    }

    func testValidStandardMenubarHandler_returnMenubarTitle() {
        let store = makeMockStore()
        store.setTimezones(nil)

        // Save a menubar selected timezone
        let dataObject = TimezoneData(with: mumbai)
        dataObject.isFavourite = 1
        saveObject(object: dataObject, in: store)

        let menubarTimezones = store.menubarTimezones()
        XCTAssertTrue(menubarTimezones?.count == 1, "Count is \(String(describing: menubarTimezones?.count))")
    }

    func testUnfavouritedTimezone_returnEmptyMenubarTimezoneCount() {
        let store = makeMockStore()
        // Wipe all timezones from UserDefaults
        store.setTimezones(nil)

        // Save a menubar selected timezone
        let dataObject = TimezoneData(with: mumbai)
        dataObject.isFavourite = 0
        saveObject(object: dataObject, in: store)

        let menubarTimezones = store.menubarTimezones()
        XCTAssertTrue(menubarTimezones?.count == 0)
    }

    func testUnfavouritedTimezone_returnNilMenubarString() {
        let store = makeMockStore()
        // Wipe all timezones from UserDefaults
        store.setTimezones(nil)
        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        let emptyMenubarString = menubarHandler.titleForMenubar()
        // Returns early because DataStore.menubarTimezones is nil
        XCTAssertNil(emptyMenubarString)

        // Save a menubar selected timezone
        let dataObject = TimezoneData(with: mumbai)
        dataObject.isFavourite = 0
        saveObject(object: dataObject, in: store)

        let menubarString = menubarHandler.titleForMenubar() ?? ""

        // Test menubar string is absent
        XCTAssertTrue(menubarString.count == 0)
    }

    func testWithEmptyMenubarTimezones() {
        let store = makeMockStore()
        store.setTimezones(nil)
        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        XCTAssertNil(menubarHandler.titleForMenubar())
    }

    func testWithStandardMenubarMode() {
        // Set mode to standard mode
        let store = makeMockStore(with: 0)
        // Save a menubar selected timezone
        let dataObject = TimezoneData(with: mumbai)
        dataObject.isFavourite = 1
        saveObject(object: dataObject, in: store)

        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        XCTAssertNil(menubarHandler.titleForMenubar())
    }

    func testProviderPassingAllConditions() {
        // Set mode to standard mode
        let store = makeMockStore()
        // Save a menubar selected timezone
        let dataObject = TimezoneData(with: mumbai)
        dataObject.isFavourite = 1
        saveObject(object: dataObject, in: store)

        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        XCTAssertNotNil(menubarHandler.titleForMenubar())
    }

    func testFormattedUpcomingEvent() {
        let store = makeMockStore()

        let futureChunk = TimeChunk(seconds: 10, minutes: 10, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
        let mockEvent = EKEvent(eventStore: eventStore)
        mockEvent.title = "Mock Title"
        mockEvent.startDate = Date().add(futureChunk)

        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        XCTAssert(menubarHandler.format(event: mockEvent) == "Mock Title in 10m",
                  "Suffix \(menubarHandler.format(event: mockEvent)) doesn't match expectation")
    }

    func testUpcomingEventHappeningWithinOneMinute() {
        let store = makeMockStore()

        let futureChunk = TimeChunk(seconds: 10, minutes: 1, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
        let mockEvent = EKEvent(eventStore: eventStore)
        mockEvent.title = "Mock Title"
        mockEvent.startDate = Date().add(futureChunk)

        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        XCTAssert(menubarHandler.format(event: mockEvent) == "Mock Title in 1m",
                  "Suffix \(menubarHandler.format(event: mockEvent)) doesn't match expectation")
    }

    func testUpcomingEventHappeningWithinSeconds() {
        let store = makeMockStore()

        let futureChunk = TimeChunk(seconds: 10, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
        let mockEvent = EKEvent(eventStore: eventStore)
        mockEvent.title = "Mock Title"
        mockEvent.startDate = Date().add(futureChunk)

        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        XCTAssert(menubarHandler.format(event: mockEvent) == "Mock Title starts now.",
                  "Suffix \(menubarHandler.format(event: mockEvent)) doesn't match expectation")
    }

    func testEmptyUpcomingEvent() {
        let store = makeMockStore()

        let futureChunk = TimeChunk(seconds: 10, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
        let mockEvent = EKEvent(eventStore: eventStore)
        mockEvent.startDate = Date().add(futureChunk)

        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        XCTAssert(menubarHandler.format(event: mockEvent) == CLEmptyString,
                  "Suffix \(menubarHandler.format(event: mockEvent)) doesn't match expectation")
    }

    func testLongUpcomingEvent() {
        let store = makeMockStore()

        let futureChunk = TimeChunk(seconds: 10, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
        let mockEvent = EKEvent(eventStore: eventStore)
        mockEvent.title = "Really long calendar event title that longer than the longest name"
        mockEvent.startDate = Date().add(futureChunk)

        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        XCTAssert(menubarHandler.format(event: mockEvent) == "Really long calendar event tit... starts now.",
                  "Suffix \(menubarHandler.format(event: mockEvent)) doesn't match expectation")
    }

    func testUpcomingEventHappeningInFiveMinutes() throws {
        let store = makeMockStore()

        let futureChunk = TimeChunk(seconds: 10, minutes: 5, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
        let mockEvent = EKEvent(eventStore: eventStore)
        mockEvent.title = "Event happening"
        mockEvent.calendar = EKCalendar(for: .event, eventStore: eventStore)
        mockEvent.startDate = Date().add(futureChunk)
        let eventInfo = EventInfo(event: mockEvent,
                                  isAllDay: false,
                                  meetingURL: nil,
                                  attendeStatus: .accepted)

        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        let calendar = Calendar.autoupdatingCurrent
        let events: [Date: [EventInfo]] = [calendar.startOfDay(for: Date()): [eventInfo]]
        let actualResult = try XCTUnwrap(menubarHandler.checkForUpcomingEvents(events, calendar: calendar))
        let expectedResult = "Event happening in 5m"
        XCTAssert(actualResult == expectedResult, "Actual Result \(actualResult)")
    }

    func testUpcomingEventHappeningIn29Minutes() throws {
        let store = makeMockStore()

        let futureChunk = TimeChunk(seconds: 10, minutes: 29, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
        let mockEvent = EKEvent(eventStore: eventStore)
        mockEvent.title = "Event happening"
        mockEvent.calendar = EKCalendar(for: .event, eventStore: eventStore)
        mockEvent.startDate = Date().add(futureChunk)
        let eventInfo = EventInfo(event: mockEvent,
                                  isAllDay: false,
                                  meetingURL: nil,
                                  attendeStatus: .accepted)

        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        let calendar = Calendar.autoupdatingCurrent
        let events: [Date: [EventInfo]] = [calendar.startOfDay(for: Date()): [eventInfo]]
        let actualResult = try XCTUnwrap(menubarHandler.checkForUpcomingEvents(events, calendar: calendar))
        let expectedResult = "Event happening in 29m"
        XCTAssert(actualResult == expectedResult, "Actual Result \(actualResult)")
    }

    func testUpcomingEventHappeningIn31Minutes() throws {
        let store = makeMockStore()

        let futureChunk = TimeChunk(seconds: 10, minutes: 31, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
        let mockEvent = EKEvent(eventStore: eventStore)
        mockEvent.title = "Event happening"
        mockEvent.calendar = EKCalendar(for: .event, eventStore: eventStore)
        mockEvent.startDate = Date().add(futureChunk)
        let eventInfo = EventInfo(event: mockEvent,
                                  isAllDay: false,
                                  meetingURL: nil,
                                  attendeStatus: .accepted)

        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        let calendar = Calendar.autoupdatingCurrent
        let events: [Date: [EventInfo]] = [calendar.startOfDay(for: Date()): [eventInfo]]
        XCTAssertNil(menubarHandler.checkForUpcomingEvents(events, calendar: calendar))
    }

    func testUpcomingEventHappeningIn31MinutesWithEmptyEvent() throws {
        let store = makeMockStore()

        let futureChunk = TimeChunk(seconds: 10, minutes: 31, hours: 0, days: 0, weeks: 0, months: 0, years: 0)
        let mockEvent = EKEvent(eventStore: eventStore)
        mockEvent.startDate = Date().add(futureChunk)
        mockEvent.calendar = EKCalendar(for: .event, eventStore: eventStore)
        let eventInfo = EventInfo(event: mockEvent,
                                  isAllDay: false,
                                  meetingURL: nil,
                                  attendeStatus: .accepted)

        let menubarHandler = MenubarTitleProvider(with: store, eventStore: EventCenter.sharedCenter())
        let calendar = Calendar.autoupdatingCurrent
        let events: [Date: [EventInfo]] = [calendar.startOfDay(for: Date()): [eventInfo]]
        XCTAssertNil(menubarHandler.checkForUpcomingEvents(events, calendar: calendar))
    }
}