import UIKit
import AVFoundation
import RealmSwift
import RxSwift
import RxRealm
import Intents
import CoreSpotlight
import MobileCoreServices
import os.log
import PKHUD

class RecordsController: UIViewController, UITableViewDelegate {
    
    @IBOutlet weak var tableView: UITableView!
    
    var recorder: Recorder?
    var addButton: UIBarButtonItem!
    let bag = DisposeBag()
    var recordDisposable: Disposable?
    var audioSessionObserver: NSObjectProtocol?
    var recordsDataSource: RealmSectionedDataSource<AudioRecord, AudioRecordCell>!
    
    let validLetters = Constants.pnLettersMap.keys.map(String.init)

    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let realm = try? Realm() else { return }
        
        self.addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(onAddVoiceRecord(_:)))
        self.navigationItem.rightBarButtonItem = self.addButton
        
        self.recorder = Recorder()
        
        DispatchQueue.main.async {
            self.recordsDataSource = RealmSectionedDataSource(table: self.tableView, data: realm.objects(AudioRecord.self)
                                                                .sorted(byKeyPath: "addedDate", ascending: false))
        }
        self.tableView.delegate = self
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.handleQuickActions()
    }
    
    func handleQuickActions() {
        guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
        
        switch ad.quickAction {
        case .addVoiceRecord:
            ad.quickAction = .none
            if let addButton = self.navigationItem.rightBarButtonItem {
                self.onAddVoiceRecord(addButton)
            }
            break
        default:
            break
        }
    }
    
    func donateUserActivity() {
        let activityId = "pro.aliencat.autocat.addVoiceRecord"
        let activity = NSUserActivity(activityType: activityId)
        activity.persistentIdentifier = activityId
        activity.isEligibleForSearch = true
        activity.isEligibleForPrediction = true
        activity.title = NSLocalizedString("Add new audio record", comment: "")
        activity.suggestedInvocationPhrase = "Запиши номер"
        
        let attributes = CSSearchableItemAttributeSet()
        attributes.contentType = kUTTypeItem as String
        attributes.contentDescription = NSLocalizedString("Add new plate number via audio recording", comment: "")
        activity.contentAttributeSet = attributes
        
        self.userActivity = activity
        activity.becomeCurrent()
    }
    
    func stopRecording() {
        self.recorder?.stopRecording()
    }
    
    // MARK: - Bar button handlers
    
    @objc func onAddVoiceRecord(_ sender: UIBarButtonItem) {
        guard let recorder = self.recorder else {
            HUD.flash(.labeledError(title: nil, subtitle: "Audio recorder is not available"))
            return
        }
        
        self.addButton.isEnabled = false
        
        var alert: UIAlertController?
        var url: URL!
        
        let locationObservable = LocationManager.requestCurrentLocation()
            .map(Optional.init)
            .catchErrorJustReturn(nil)
        
        let recordObservable: Single<String> = recorder.requestPermissions()
            .observeOn(MainScheduler.instance)
            .flatMap(self.makeStartSoundIfNeeded)
            .flatMap {
                #if targetEnvironment(macCatalyst) || targetEnvironment(simulator)
                    DispatchQueue.main.async {
                        alert = self.showRecordingAlert()
                    }
                #else
                    if let observer = self.audioSessionObserver {
                        NotificationCenter.default.removeObserver(observer, name: AVAudioSession.routeChangeNotification, object: nil)
                    }
                    self.audioSessionObserver = NotificationCenter.default.addObserver(forName: AVAudioSession.routeChangeNotification, object: nil, queue: .main) { notification in
                        guard let dict = notification.userInfo as? [String: Any],
                            let reasonInt = dict["AVAudioSessionRouteChangeReasonKey"] as? NSNumber,
                            let reason = AVAudioSession.RouteChangeReason(rawValue: reasonInt.uintValue),
                            let session = notification.object as? AVAudioSession else { return }
                        
                        if reason == .categoryChange && session.category == .playAndRecord {
                            alert = self.showRecordingAlert()
                        }
                    }
                #endif
                let date = Date()
                let fileName = "recording-\(date.timeIntervalSince1970).m4a"
                url = try FileManager.default.url(for: fileName, in: "recordings")
                
                return recorder.startRecording(to: url)
            }
        
        self.recordDisposable = Single.zip(locationObservable, recordObservable) { event, text -> AudioRecord in
            let asset = AVURLAsset(url: url)
            let duration = TimeInterval(CMTimeGetSeconds(asset.duration))
            return AudioRecord(path: url.lastPathComponent, number: self.getPlateNumber(from: text), raw: text, duration: duration, event: event)
        }
        .subscribe(onSuccess: { record in
            let realm = try? Realm()
            try? realm?.write {
                realm?.add(record)
            }
            alert?.dismiss(animated: true)
            self.addButton.isEnabled = true
        }) { error in
            if let alert = alert {
                alert.dismiss(animated: true) {
                    HUD.show(error: error)
                }
            } else {
                HUD.show(error: error)
            }
            self.addButton.isEnabled = true
        }
    }
    
    func showRecordingAlert() -> UIAlertController {
        let alert = UIAlertController(title: NSLocalizedString("Recording...", comment: ""), message: nil, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: { _ in self.recordDisposable?.dispose() }))
        alert.addAction(UIAlertAction(title: NSLocalizedString("Done", comment: ""), style: .default, handler: { _ in self.recorder?.stopRecording() }))
        self.present(alert, animated: true)
        return alert
    }
    
    // MARK: - Processing
    
    func getPlateNumber(from recognizedText: String) -> String? {
        let trimmed = recognizedText
            .replacingOccurrences(of: " ", with: "")
            .uppercased()
            .replacingOccurrences(of: "Ф", with: "В")
            .replacingOccurrences(of: "НОЛЬ", with: "0")
            .replacingOccurrences(of: "Э", with: "")
        
        var result = ""
        if let range = trimmed.range(of: #"\S\d\d\d\S\S\d\d\d?"#, options: .regularExpression) {
            result = String(trimmed[range])
        } else if let range = trimmed.range(of: #"\S\S\S\d\d\d\d\d\d?"#, options: .regularExpression), Settings.shared.recognizeAlternativeOrder {
            let n = String(trimmed[range])
            result = String(n.prefix(1)) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + n.substring(from: 6)
        } else if let range = trimmed.range(of: #"\S\d\d\d\S\S"#, options: .regularExpression), Settings.shared.recognizeShortenedNumbers {
            result = String(trimmed[range]) + Settings.shared.defaultRegion
        } else if let range = trimmed.range(of: #"\S\S\S\d\d\d"#, options: .regularExpression), Settings.shared.recognizeAlternativeOrder && Settings.shared.recognizeShortenedNumbers {
            let n = String(trimmed[range])
            result = String(n.prefix(1)) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + Settings.shared.defaultRegion
        }
        
        if !result.isEmpty && valid(number: result) {
            return result
        } else {
            return nil
        }
    }
    
    func valid(number: String) -> Bool {
        guard number.count >= 8 else { return false }
        
        let first = String(number.prefix(1))
        let second = number.substring(with: 4..<5)
        let third = number.substring(with: 5..<6)
        let digits = Int(number.substring(with: 1..<4))
        let region = Int(number.substring(from: 6))
        
        return self.validLetters.contains(first)
            && self.validLetters.contains(second)
            && self.validLetters.contains(third)
            && digits != nil
            && region != nil
            && region! < 1000
    }
    
    func makeStartSoundIfNeeded() -> Single<Void> {
        if !Settings.shared.recordBeep {
            return .just(())
        } else {
            return Single<Void>.create { observer in
                var soundId = SystemSoundID()
                let url = URL(fileURLWithPath: "/System/Library/Audio/UISounds/short_double_high.caf")
                AudioServicesCreateSystemSoundID(url as CFURL, &soundId)
                AudioServicesPlaySystemSoundWithCompletion(soundId) {
                    observer(.success(()))
                }
                return Disposables.create()
            }
        }
    }

    // MARK: - UITableViewDelegate
    
    func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        let record = self.recordsDataSource.item(at: indexPath)
        
        return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
            let check = UIAction(title: NSLocalizedString("Check", comment: ""), image: UIImage(systemName: "eye")) { action in
                if let number = record.number {
                    self.check(number: number, event: record.event)
                }
            }
            
            let delete = UIAction(title: NSLocalizedString("Delete", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive) { action in
                self.delete(record: record)
            }
            
            let share = UIAction(title: NSLocalizedString("Share", comment: ""), image: UIImage(systemName: "square.and.arrow.up")) { action in
                self.share(record: record)
            }
            
            let showText = UIAction(title: NSLocalizedString("Show recognized text", comment: ""), image: UIImage(systemName: "textformat")) { action in
                self.showAlert(title: NSLocalizedString("Recognized text", comment: ""), message: record.rawText)
            }
            
            let showMap = UIAction(title: NSLocalizedString("Show on map", comment: ""), image: UIImage(systemName: "mappin.and.ellipse")) { action in
                self.showOnMap(record)
            }
            
            let edit = UIAction(title: NSLocalizedString("Edit plate number", comment: ""), image: UIImage(systemName: "pencil")) { action in
                self.edit(record: record)
            }
            
            var actions = [edit, showText, showMap, share, delete]
            if record.number != nil {
                actions.insert(check, at: 0)
            }

            return UIMenu(title: NSLocalizedString("Actions", comment: ""), children: actions)
        }
    }
    
    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        guard let cell = tableView.cellForRow(at: indexPath) else { return nil }
        
        let record = self.recordsDataSource.item(at: indexPath)
        
        let check = UIContextualAction(style: .normal, title: NSLocalizedString("Check", comment: "")) { action, view, completion in
            if let number = record.number {
                self.check(number: number, event: record.event)
            }
            completion(true)
        }
        check.backgroundColor = .systemGray2
        check.image = UIImage(systemName: "eye")
        
        let action = UIContextualAction(style: .normal, title: NSLocalizedString("Action", comment: "")) { action, view, completion in
            self.moreActions(for: record, cell: cell)
            completion(true)
        }
        action.backgroundColor = .systemGray2
        action.image = UIImage(systemName:  "ellipsis" /*"square.and.arrow.up"*/)
        
        let delete = UIContextualAction(style: .destructive, title: NSLocalizedString("Delete", comment: "")) { action, view, completion in
            self.delete(record: record)
            completion(true)
        }
        delete.image = UIImage(systemName: "trash")
        
        let actions = record.number == nil ? [delete, action] : [delete, check, action]
        let configuration = UISwipeActionsConfiguration(actions: actions)
        configuration.performsFirstActionWithFullSwipe = false
        return configuration
    }
    
    func moreActions(for record: AudioRecord, cell: UITableViewCell) {
        let sheet = UIAlertController(title: NSLocalizedString("More actions", comment: ""), message: nil, preferredStyle: .actionSheet)
        let cancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { _ in sheet.dismiss(animated: true, completion: nil) }
        let share = UIAlertAction(title: NSLocalizedString("Share", comment: ""), style: .default) { _ in
            self.share(record: record)
        }
        let showText = UIAlertAction(title: NSLocalizedString("Show recognized text", comment: ""), style: .default) { action in
            self.showAlert(title: NSLocalizedString("Recognized text", comment: ""), message: record.rawText)
        }
        let editNumber = UIAlertAction(title: NSLocalizedString("Edit plate number", comment: ""), style: .default) { action in
            self.edit(record: record)
        }
        let showOnMap = UIAlertAction(title: NSLocalizedString("Show on map", comment: ""), style: .default) { action in
            self.showOnMap(record)
        }
        
        sheet.addAction(editNumber)
        sheet.addAction(showText)
        if record.event != nil {
            sheet.addAction(showOnMap)
        }
        sheet.addAction(share)
        sheet.addAction(cancel)
        sheet.popoverPresentationController?.sourceView = cell
        sheet.popoverPresentationController?.sourceRect = cell.frame
        self.present(sheet, animated: true, completion: nil)
    }
    
    func check(number: String, event: VehicleEvent?) {
        guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
        ad.quickAction = .checkNumber(number, event)
        self.tabBarController?.selectedIndex = 0
    }
    
    func edit(record: AudioRecord) {
        let alert = UIAlertController(title: NSLocalizedString("Edit plate number", comment: ""), message: nil, preferredStyle: .alert)
        let done = UIAlertAction(title: NSLocalizedString("Done", comment: ""), style: .default) { action in
            guard let tf = alert.textFields?.first else { return }
            if let realm = try? Realm() {
                try? realm.write {
                    record.number = tf.text?.uppercased()
                }
            }
        }
        alert.addAction(done)
        alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: { action in
            alert.dismiss(animated: true)
        }))
        alert.addTextField { tf in
            tf.text = record.number ?? record.rawText.replacingOccurrences(of: " ", with: "")
            NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: tf, queue: OperationQueue.main) { _ in
                done.isEnabled = self.valid(number: tf.text?.uppercased() ?? "")
            }
        }
        self.present(alert, animated: true)
    }
    
    func delete(record: AudioRecord) {
        do {
            if let realm = record.realm {
                try realm.write {
                    realm.delete(record)
                }
            }
        } catch {
            print(error)
        }
    }
    
    func share(record: AudioRecord) {
        do {
            let url = try FileManager.default.url(for: record.path, in: "recordings")
            let controller = UIActivityViewController(activityItems: [url], applicationActivities: nil)
            self.present(controller, animated: true)
        } catch {
            print("Error sharing audio record: \(error.localizedDescription)")
            HUD.show(error: error)
        }
    }
    
    func showOnMap(_ record: AudioRecord) {
        let controller = ShowEventController()
        controller.event = record.event
        controller.hidesBottomBarWhenPushed = true
        self.navigationController?.pushViewController(controller, animated: true)
    }
}
