import UIKit
import RealmSwift
import RxSwift
import SwiftDate
import RxRealm
import PKHUD
import CoreLocation

enum EventAction: Equatable {
    case doNotSend
    case receiveAndSend
    case sendSpecific(VehicleEvent)
}

enum HistoryFilter {
    case all
    case unrecognized
    case outdated
}

extension String.StringInterpolation {
    mutating func appendInterpolation(_ value: EventAction) {
        switch value {
        case .doNotSend: appendLiteral("do not send"); break
        case .receiveAndSend: appendLiteral("receive and send"); break
        case .sendSpecific(let event): appendLiteral("send specific (\(event.latitude), \(event.longitude)"); break
        }
    }
}

class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegate, PNKeyboardDelegate {

    @IBOutlet weak var number: SwiftMaskTextfield!
    @IBOutlet weak var check: UIButton!
    @IBOutlet weak var history: UITableView!
    
    private let bag = DisposeBag()
    private var historyDataSource: RealmSectionedDataSource<Vehicle, VehicleCell>!
    private var historyFilter: HistoryFilter = .all
    
    // MARK: - Lifecycle
    
    override func viewDidLoad() {
        super.viewDidLoad()

        guard let realm = try? Realm() else { return }
        
        self.hideKeyboardWhenTappedAround()
        let keyboard = PNKeyboard(target: self.number)
        keyboard.delegate = self
        self.number.formatPattern = "@###@@###"
        self.number.inputView = keyboard
        self.check.isEnabled = false
        
        DispatchQueue.main.async {
            self.historyDataSource = RealmSectionedDataSource(table: self.history, data: realm.objects(Vehicle.self).sorted(byKeyPath: "updatedDate", ascending: false))
        }
        
        self.history.delegate = self
        NotificationCenter.default.addObserver(self, selector:#selector(self.calendarDayDidChange(_:)), name:NSNotification.Name.NSCalendarDayChanged, object:nil)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        //self.navigationController?.setNavigationBarHidden(true, animated: false)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        if let index = self.history.indexPathForSelectedRow {
            self.history.deselectRow(at: index, animated: true)
        }
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.handleQuickActions()
    }
    
    // MARK: -
    
    func handleQuickActions() {
        guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
        
        switch ad.quickAction {
        case .check:
            ad.quickAction = .none
            self.number.becomeFirstResponder()
            break
        case .checkNumber(let number, let event):
            ad.quickAction = .none
            var action: EventAction = .receiveAndSend
            if let event = event {
                action = .sendSpecific(event)
            }
            HUD.show(.progress)
            self.check(number: number, action: action).subscribe { (vehicle, errors) in
                if !vehicle.unrecognized {
                    self.updateDetailController(with: vehicle)
                }
                HUD.hide()
                self.showErrors(errors)
            } onError: { error in
                HUD.hide()
                self.show(error: error)
                //HUD.show(error: error)
            }
            .disposed(by: self.bag)
            break
        case .addVoiceRecord:
            self.tabBarController?.selectedIndex = 1
            break
        case .openReport(let number):
            ad.quickAction = .none
            if let sd = self.view.window?.windowScene?.delegate as? SceneDelegate {
                sd.openReport(with: number)
            }
            break
        default:
            break
        }
    }
    
    @objc private func calendarDayDidChange(_ notification : NSNotification) {
        DispatchQueue.main.async {
            self.historyDataSource.reload()
        }
    }
    
    // MARK: - Actions
    
    @IBAction func onExport(_ sender: UIBarButtonItem) {
        let sheet = UIAlertController(title: NSLocalizedString("Export history as", comment: ""), message: nil, preferredStyle: .actionSheet)
        let cancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { _ in sheet.dismiss(animated: true, completion: nil) }
        
        let csv = UIAlertAction(title: NSLocalizedString("CSV table", comment: "export as CSV table"), style: .default) { action in
            do {
                let csvString = try self.historyDataSource.makeCsv()
                let tmpUrl = FileManager.default.tmpUrl(name: "history", ext: "csv")
                try csvString.write(to: tmpUrl, atomically: true, encoding: .utf8)
                self.shareFile(tmpUrl)
            } catch {
                self.show(error: error)
            }
        }
        
        let db = UIAlertAction(title: NSLocalizedString("Database file", comment: "export as database file"), style: .default) { action in
            do {
                let realm = try Realm()
                let tmpUrl = FileManager.default.tmpUrl(name: "history", ext: "realm")
                if let dbUrl = realm.configuration.fileURL {
                    try FileManager.default.copyItem(at: dbUrl, to: tmpUrl)
                    self.shareFile(tmpUrl)
                }
            } catch {
                self.show(error: error)
            }
        }
        
        sheet.addAction(csv)
        sheet.addAction(db)
        sheet.addAction(cancel)
        sheet.popoverPresentationController?.barButtonItem = sender
        self.present(sheet, animated: true)
    }
    
    @IBAction func onFilter(_ sender: UIBarButtonItem) {
        let sheet = UIAlertController(title: NSLocalizedString("Filter check history", comment: ""), message: nil, preferredStyle: .actionSheet)
        let cancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { _ in sheet.dismiss(animated: true, completion: nil) }
        let all = UIAlertAction(title: NSLocalizedString("All", comment: ""), style: .default) { action in
            self.historyDataSource.setFilterPredicate(nil)
            self.historyFilter = .all
        }
        let unrecognized = UIAlertAction(title: NSLocalizedString("Unrecognized", comment: ""), style: .default) { _ in
            self.historyDataSource.setFilterPredicate { $0.unrecognized }
            self.historyFilter = .unrecognized
        }
        let outdated = UIAlertAction(title: NSLocalizedString("Outdated", comment: ""), style: .default) { _ in
            self.historyDataSource.setFilterPredicate { $0.outdated }
            self.historyFilter = .outdated
        }
        
        switch self.historyFilter {
        case .all: all.setValue(true, forKey: "checked")
        case .unrecognized: unrecognized.setValue(true, forKey: "checked")
        case .outdated: outdated.setValue(true, forKey: "checked")
        }
        
        sheet.addAction(all)
        sheet.addAction(unrecognized)
        sheet.addAction(outdated)
        sheet.addAction(cancel)
        sheet.popoverPresentationController?.barButtonItem = sender
        self.present(sheet, animated: true)
    }
    
    func shareFile(_ url: URL) {
        let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
        self.present(activityController, animated: true) {
            print("")
        }
        print("")
    }
    
    // MARK: - Checking new number
    
    @IBAction func checkTapped(_ sender: UIButton) {
        guard let number = self.number.text else { return }
		
		let numberNormalized = number.filter { !$0.isWhitespace }.uppercased()
        self.number.resignFirstResponder()
        self.number.text = nil
        self.check.isEnabled = false
        
        HUD.show(.progress)
        self.check(number: numberNormalized, action: .receiveAndSend).subscribe { (vehicle, errors) in
            if !vehicle.unrecognized {
                self.updateDetailController(with: vehicle)
            }
            HUD.hide()
            self.showErrors(errors)
        } onError: { error in
            HUD.hide()
            self.show(error: error)
        }
        .disposed(by: self.bag)

    }
    
    func updateDetailController(with vehicle: Vehicle) {
        if let splitViewController = self.view.window?.rootViewController as? UISplitViewController
        {
            var detail: UINavigationController?
            if splitViewController.viewControllers.count == 2 {
                detail = splitViewController.viewControllers.last as? UINavigationController
            } else {
                let storyboard = UIStoryboard(name: "Main", bundle: nil)
                detail = storyboard.instantiateViewController(identifier: "ReportNavController")
            }
            
            if let detail = detail {
                detail.popToRootViewController(animated: true)
                let report = detail.viewControllers.first as? ReportController
                report?.number = vehicle.getNumber()
                splitViewController.showDetailViewController(detail, sender: self)
                //self.performSegue(withIdentifier: "OpenDetailSegue", sender: self)
            }
        }
    }
    
    // MARK: - UITextFieldDelegate
    
    @IBAction func textFieldDidBeginEditing(_ textField: UITextField) {
        LocationManager.requestCurrentLocation().subscribe().disposed(by: self.bag)
    }
    
    @IBAction func textFieldDidChange(_ textField: UITextField) {
        guard let text = textField.text else {
            self.check.isEnabled = false
            return
        }
        
        self.check.isEnabled = text.count >= 8
    }
    
    // MARK: - PNKeyboardDelegate
    
    func returnClicked() {
        if self.check.isEnabled {
            self.checkTapped(self.check)
        }
    }
    
    // MARK: - UITableViewDelegate
    
    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let vehicle = self.historyDataSource.item(at: indexPath)
        
        let updateAction = UIContextualAction(style: .normal, title: NSLocalizedString("Update", comment: "")) { action, view, completion in
            self.update(vehicle: vehicle)
            completion(true)
        }
        updateAction.image = UIImage(systemName: "arrow.2.circlepath")
        updateAction.backgroundColor = .systemBlue
        
        let removeAction = UIContextualAction(style: .destructive, title: NSLocalizedString("Remove", comment: "")) { action, view, completion in
            self.remove(vehicle: vehicle)
        }
        removeAction.image = UIImage(systemName: "trash")
        
        let configuration = UISwipeActionsConfiguration(actions: [updateAction, removeAction])
        configuration.performsFirstActionWithFullSwipe = false
        return configuration
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let vehicle = self.historyDataSource.item(at: indexPath)
        self.updateDetailController(with: vehicle)
    }
    
    func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        let vehicle = self.historyDataSource.item(at: indexPath)
        return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
            let update = UIAction(title: NSLocalizedString("Update", comment: ""), image: UIImage(systemName: "arrow.2.circlepath")) { action in
                self.update(vehicle: vehicle)
            }
            let remove = UIAction(title: NSLocalizedString("Remove", comment: ""), image: UIImage(systemName: "trash"), attributes: [.destructive]) { action in
                self.remove(vehicle: vehicle)
            }
            
            return UIMenu(title: NSLocalizedString("Actions", comment: ""), children: [update, remove])
        }
    }
    
    // MARK: - Contextual actions
    
    func update(vehicle: Vehicle) {
        HUD.show(.progress)
        
        var eventAction: EventAction = .doNotSend
        if vehicle.unrecognized, let savedEvent = vehicle.events.first {
            eventAction = .sendSpecific(savedEvent)
        }
        
        self.check(number: vehicle.getNumber(), action: eventAction, force: true).subscribe { (vehicle, errors) in
            if !vehicle.unrecognized {
                self.updateDetailController(with: vehicle)
            }
            HUD.hide()
            self.showErrors(errors)
        } onError: { error in
            HUD.hide()
            self.show(error: error)
        }
        .disposed(by: self.bag)
    }
    
    func remove(vehicle: Vehicle) {
        guard let realm = try? Realm() else { return }
        guard let realmVehicle = realm.object(ofType: Vehicle.self, forPrimaryKey: vehicle.getNumber()) else { return }
        
        do {
            try realm.write {
                realm.delete(realmVehicle)
            }
        } catch {
            print(error)
        }
    }
    
    // MARK: - Checking number
    
    func save(vehicle: Vehicle) throws {
        let realm = try Realm()
        try realm.write {
            realm.add(vehicle, update: .all)
        }
    }
    
    func getEvent(for action: EventAction) -> Single<VehicleEvent> {
        if case .sendSpecific(let event) = action {
            return Single.just(event)
        }
        
        if let event = LocationManager.lastEvent, (Date().timeIntervalSince1970 - event.date) < 100  {
            return Single<VehicleEvent>.just(event)
        } else {
            return LocationManager.requestCurrentLocation()
        }
    }
    
    func check(number: String, action: EventAction, force: Bool = false) -> Single<(vehicle: Vehicle, errors: [Error])> {
        var eventSingle: Single<(event: VehicleEvent?, error: Error?)> = .just((event: nil, error: nil))
        if action != .doNotSend {
            eventSingle = self.getEvent(for: action)
                .flatMap { event in event.findAddress().map{ event }.catchErrorJustReturn(event) }
                .map { event -> (event: VehicleEvent?, error: Error?) in (event: event, error: nil) }
                .observeOn(MainScheduler.instance)
                .catchError { .just((event: nil, error: $0)) }
        }
        
        let checkSingle = Api.checkVehicle(by: number, force: force)
            .observeOn(MainScheduler.instance)
            .map { (vehicle: Vehicle) -> (vehicle: Vehicle, error: Error?) in
                try self.save(vehicle: vehicle)
                return (vehicle: vehicle, error: nil)
            }
            .catchError { error  in
                let realm = try Realm()
                if let existingVehicle = realm.object(ofType: Vehicle.self, forPrimaryKey: number) {
                    return .just((vehicle: existingVehicle, error: error))
                } else {
                    let vehicle = Vehicle(number)
                    try realm.write { realm.add(vehicle, update: .all) }
                    return .just((vehicle: vehicle, error: error))
                }
            }
        
        return Single.zip(eventSingle, checkSingle).flatMap { eventResult, vehicleResult in
            let errors = [eventResult.error, vehicleResult.error].map { error -> Error? in
                if let clerror = error as? CLError {
                    if clerror.code != .denied {
                        return CocoaError.error(NSLocalizedString("Location error", comment: ""), reason: clerror.code.description)
                    } else {
                        return nil
                    }
                } else {
                    return error
                }
            }
            .compactMap { $0 }
            
            LocationManager.resetLastEvent()
            
            if vehicleResult.vehicle.unrecognized {
                let realm = try Realm()
                if let event = eventResult.event {
                    try realm.write { vehicleResult.vehicle.events.append(event) }
                }
                return .just((vehicle: vehicleResult.vehicle, errors: errors))
            } else {
                if let event = eventResult.event {
                    return Api.add(event: event, to: vehicleResult.vehicle.getNumber())
                        .observeOn(MainScheduler.instance)
                        .map {
                            try self.save(vehicle: $0)
                            return (vehicle: $0, errors: errors)
                        }
                } else {
                    return .just((vehicle: vehicleResult.vehicle, errors: errors))
                }
            }
        }
    }
    
    func showErrors(_ errors: [Error]) {
        let observables = errors.map(rxShowError)
        Observable.from(observables).concat().subscribe().disposed(by: self.bag)
    }
    
    func rxShowError(_ error: Error) -> Observable<Void> {
        return Observable<Void>.create { observer in
            self.show(error: error, animated: true) {
                observer.on(.next(()))
                observer.on(.completed)
            }
            return Disposables.create()
        }
    }
}
