import Communicator
import EMTLoadingIndicator
import Foundation
import PromiseKit
import RealmSwift
import Shared
import WatchKit

class InterfaceController: WKInterfaceController {
    @IBOutlet var tableView: WKInterfaceTable!
    @IBOutlet var noActionsLabel: WKInterfaceLabel!

    var notificationToken: NotificationToken?

    var actions: Results<Action>?

    override func awake(withContext context: Any?) {
        super.awake(withContext: context)

        MaterialDesignIcons.register()

        setupTable()
    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

    func setupTable() {
        let realm = Realm.live()

        noActionsLabel.setText(L10n.Watch.Labels.noAction)

        let actions = realm.objects(Action.self).sorted(byKeyPath: "Position")
        self.actions = actions

        notificationToken = actions.observe { (changes: RealmCollectionChange) in
            guard let tableView = self.tableView else { return }

            self.noActionsLabel.setHidden(actions.count > 0)

            switch changes {
            case .initial:
                // Results are now populated and can be accessed without blocking the UI
                self.tableView.setNumberOfRows(actions.count, withRowType: "actionRowType")

                for idx in actions.indices {
                    self.setupRow(idx)
                }
            case let .update(_, deletions, insertions, modifications):
                let insertionsSet = NSMutableIndexSet()
                insertions.forEach(insertionsSet.add)

                tableView.insertRows(at: IndexSet(insertionsSet), withRowType: "actionRowType")

                insertions.forEach(self.setupRow)

                let deletionsSet = NSMutableIndexSet()
                deletions.forEach(deletionsSet.add)

                tableView.removeRows(at: IndexSet(deletionsSet))

                modifications.forEach(self.setupRow)
            case let .error(error):
                // An error occurred while opening the Realm file on the background worker thread
                Current.Log.error("Error during Realm notifications! \(error)")
            }
        }
    }

    func setupRow(_ index: Int) {
        DispatchQueue.main.async {
            guard let row = self.tableView.rowController(at: index) as? ActionRowType,
                  let action = self.actions?[index] else { return }
            row.group.setBackgroundColor(UIColor(hex: action.BackgroundColor))
            row.indicator = EMTLoadingIndicator(
                interfaceController: self,
                interfaceImage: row.image,
                width: 24,
                height: 24,
                style: .dot
            )
            row.icon = MaterialDesignIcons(named: action.IconName)
            let iconColor = UIColor(hex: action.IconColor)
            row.image.setImage(row.icon.image(ofSize: CGSize(width: 24, height: 24), color: iconColor))
            row.image.setAlpha(1)
            row.label.setText(action.Text)
            row.label.setTextColor(UIColor(hex: action.TextColor))
        }
    }

    override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) {
        let selectedAction = actions![rowIndex]

        Current.Log.verbose("Selected action row at index \(rowIndex), \(selectedAction)")

        guard let row = tableView.rowController(at: rowIndex) as? ActionRowType else {
            Current.Log.warning("Row at \(rowIndex) is not ActionRowType")
            return
        }

        row.indicator?.prepareImagesForWait()
        row.indicator?.showWait()

        enum SendError: Error {
            case notImmediate
            case phoneFailed
        }

        firstly { () -> Promise<Void> in
            Promise { seal in
                guard Communicator.shared.currentReachability == .immediatelyReachable else {
                    seal.reject(SendError.notImmediate)
                    return
                }

                Current.Log.verbose("Signaling action pressed via phone")
                let actionMessage = InteractiveImmediateMessage(
                    identifier: "ActionRowPressed",
                    content: ["ActionID": selectedAction.ID],
                    reply: { message in
                        Current.Log.verbose("Received reply dictionary \(message)")
                        if message.content["fired"] as? Bool == true {
                            seal.fulfill(())
                        } else {
                            seal.reject(SendError.phoneFailed)
                        }
                    }
                )

                Current.Log.verbose("Sending ActionRowPressed message \(actionMessage)")
                Communicator.shared.send(actionMessage, errorHandler: { error in
                    Current.Log.error("Received error when sending immediate message \(error)")
                    seal.reject(error)
                })
            }
        }.recover { error -> Promise<Void> in
            guard error == SendError.notImmediate, let server = Current.servers.server(for: selectedAction) else {
                throw error
            }

            Current.Log.error("recovering error \(error) by trying locally")
            return Current.api(for: server).HandleAction(actionID: selectedAction.ID, source: .Watch)
        }.done {
            self.handleActionSuccess(row, rowIndex)
        }.catch { err -> Void in
            Current.Log.error("Error during action event fire: \(err)")
            self.handleActionFailure(row, rowIndex)
        }
    }

    func handleActionSuccess(_ row: ActionRowType, _ index: Int) {
        WKInterfaceDevice.current().play(.success)

        row.image.stopAnimating()

        setupRow(index)
    }

    func handleActionFailure(_ row: ActionRowType, _ index: Int) {
        WKInterfaceDevice.current().play(.failure)

        row.image.stopAnimating()

        setupRow(index)
    }

    deinit {
        notificationToken?.invalidate()
    }
}