//
//  NotificationsViewController.swift
//  Freetime
//
//  Created by Ryan Nystrom on 6/8/18.
//  Copyright © 2018 Ryan Nystrom. All rights reserved.
//

import UIKit
import IGListKit
import FlatCache
import Squawk

final class NotificationsViewController: BaseListViewController2<Int>,
BaseListViewController2DataSource,
FlatCacheListener,
TabNavRootViewControllerType,
BaseListViewController2EmptyDataSource {

    private let modelController: NotificationModelController
    private let inboxType: InboxType
    private var notificationIDs = [String]()

    private var notifications: [NotificationViewModel] {
        return notificationIDs.compactMap { modelController.githubClient.cache.get(id: $0) }
    }

    init(modelController: NotificationModelController, inboxType: InboxType) {
        self.modelController = modelController
        self.inboxType = inboxType

        super.init(
            emptyErrorMessage: NSLocalizedString("Cannot load your inbox.", comment: ""),
            backgroundThreshold: 5 * 60
        )

        self.dataSource = self
        self.emptyDataSource = self

        switch inboxType {
        case .all: title = NSLocalizedString("All", comment: "")
        case .unread: title = NSLocalizedString("Inbox", comment: "")
        case .repo(let repo): title = repo.name
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        makeBackBarItemEmpty()
        resetRightBarItem()

        switch inboxType {
        case .unread:
            let item = UIBarButtonItem(
                image: UIImage(named: "bullets-hollow"),
                style: .plain,
                target: self,
                action: #selector(NotificationsViewController.onMore(sender:))
            )
            item.accessibilityLabel = Constants.Strings.moreOptions
            navigationItem.leftBarButtonItem = item
        case .repo, .all: break
        }

        navigationController?.tabBarItem.badgeColor = Styles.Colors.Red.medium.color
    }

    override func fetch(page: Int?) {
        let width = view.bounds.width
        let showAll = inboxType.showAll

        let repo: Repository?
        switch inboxType {
        case .repo(let r): repo = r
        case .all, .unread: repo = nil
        }

        if let page = page {
            modelController.fetchNotifications(repo: repo, all: showAll, page: page, width: width) { [weak self] result in
                self?.handle(result: result, append: true, animated: false, page: page)
            }
        } else {
            let first = 1
            modelController.fetchNotifications(repo: repo, all: showAll, page: first, width: width) { [weak self] result in
                self?.handle(result: result, append: false, animated: trueUnlessReduceMotionEnabled, page: first)
            }
        }
    }

    private func handle(result: Result<([NotificationViewModel], Int?)>, append: Bool, animated: Bool, page: Int) {
        switch result {
        case .success(let notifications, let next):
            var ids = [String]()
            notifications.forEach {
                modelController.githubClient.cache.add(listener: self, value: $0)
                ids.append($0.id)
            }
            rebuildAndUpdate(ids: ids, append: append, page: next, animated: animated)
        case .error(let err):
            error(animated: trueUnlessReduceMotionEnabled)
            Squawk.show(error: err)
        }

        // set after updating so self.models has already been changed
        updateUnreadState()
    }

    private func updateUnreadState() {
        // don't update tab bar and badges when not showing only new notifications
        // prevents archives updating badge and tab #s
        switch inboxType {
        case .repo: return
        case .all, .unread: break
        }

        var unread = 0
        for id in notificationIDs {
            guard let model = modelController.githubClient.cache.get(id: id) as NotificationViewModel?,
                !model.read
                else { continue }
            unread += 1
        }

        let hasUnread = unread > 0
        navigationItem.rightBarButtonItem?.isEnabled = hasUnread
        navigationController?.tabBarItem.badgeValue = hasUnread ? "\(unread)" : nil
        BadgeNotifications.updateBadge(count: unread)
    }

    @objc func onMore(sender: UIBarButtonItem) {
        let alert = UIAlertController.configured(preferredStyle: .actionSheet)

        alert.add(action: UIAlertAction(
            title: NSLocalizedString("View All", comment: ""),
            style: .default,
            handler: { [weak self] _ in
                self?.onViewAll()
        }))

        let cache = modelController.githubClient.cache
        var repoNames = Set<String>()
        for id in notificationIDs {
            guard let model = cache.get(id: id) as NotificationViewModel?,
                !repoNames.contains(model.repo)
                else { continue }
            repoNames.insert(model.repo)
            alert.add(action: UIAlertAction(title: model.repo, style: .default, handler: { [weak self] _ in
                self?.pushRepoNotifications(owner: model.owner, repo: model.repo)
            }))
        }

        alert.add(action: AlertAction.cancel())
        alert.popoverPresentationController?.barButtonItem = sender
        present(alert, animated: trueUnlessReduceMotionEnabled)
    }

    func pushRepoNotifications(owner: String, repo: String) {
        let controller = NotificationsViewController(
            modelController: modelController,
            inboxType: .repo(Repository(owner: owner, name: repo))
        )
        navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled)
    }

    func onViewAll() {
        let controller = NotificationsViewController(
            modelController: modelController,
            inboxType: .all
        )
        navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled)
    }

    func resetRightBarItem(updatingState updateState: Bool = true) {
        let item = UIBarButtonItem(
            image: UIImage(named: "check"),
            style: .plain,
            target: self,
            action: #selector(onMarkAll)
        )
        item.accessibilityLabel = NSLocalizedString("Mark notifications read", comment: "")
        navigationItem.rightBarButtonItem = item
        if updateState {
            updateUnreadState()
        }
    }

    @objc private func onMarkAll() {
        let message: String
        switch inboxType {
        case .all, .unread:
            message = NSLocalizedString("Mark all notifications as read?", comment: "")
        case .repo(let repo):
            let messageFormat = NSLocalizedString("Mark %@ notifications as read?", comment: "")
            message = String(format: messageFormat, repo.name)
        }

        let alert = UIAlertController.configured(
            title: NSLocalizedString("Notifications", comment: ""),
            message: message,
            preferredStyle: .alert
        )

        alert.addActions([
            UIAlertAction(
                title: Constants.Strings.markRead,
                style: .destructive,
                handler: { [weak self] _ in
                    self?.markRead()
            }),
            AlertAction.cancel()
            ])

        present(alert, animated: trueUnlessReduceMotionEnabled)
    }

    private func markRead() {
        self.setRightBarItemSpinning()

        let block: (Bool) -> Void = { success in
            let generator = UINotificationFeedbackGenerator()
            if success {
                generator.notificationOccurred(.success)

                // clear all badges
                BadgeNotifications.updateBadge(count: 0)

                // change the spinner to the mark all item
                // don't update state here; it is managed by `fetch`
                self.resetRightBarItem(updatingState: false)
            } else {
                generator.notificationOccurred(.error)
            }
            self.fetch(page: nil)

            // "mark all" is an engaging action, system prompt on it
            RatingController.prompt(.system)
        }

        switch inboxType {
        case .all, .unread: modelController.markAllNotifications(completion: block)
        case .repo(let repo): modelController.markRepoNotifications(repo: repo, completion: block)
        }
    }

    private func rebuildAndUpdate(
        ids: [String],
        append: Bool,
        page: Int?,
        animated: Bool
        ) {
        if append {
            notificationIDs += ids
        } else {
            notificationIDs = ids
        }
        update(page: page, animated: animated)
    }

    // MARK: BaseListViewController2DataSource

    func models(adapter: ListSwiftAdapter) -> [ListSwiftPair] {
        return notificationIDs.compactMap { id in
            guard let model = modelController.githubClient.cache.get(id: id) as NotificationViewModel?
                else { return nil }
            return ListSwiftPair.pair(model) { [modelController] in
                NotificationSectionController(modelController: modelController)
            }
        }
    }

    // MARK: BaseListViewController2EmptyDataSource

    func emptyModel(for adapter: ListSwiftAdapter) -> ListSwiftPair {
        let layoutInsets = view.safeAreaInsets
        return ListSwiftPair.pair("empty-notification-value", {
            return NoNewNotificationSectionController(layoutInsets: layoutInsets)
        })
    }

    // MARK: FlatCacheListener

    func flatCacheDidUpdate(cache: FlatCache, update: FlatCache.Update) {
        self.update(animated: trueUnlessReduceMotionEnabled)
        updateUnreadState()
    }

    // MARK: TabNavRootViewControllerType

    func didSingleTapTab() {
        feed.collectionView.scrollToTop(animated: true)
    }

    func didDoubleTapTab() {
        didSingleTapTab()
    }

}
