//  OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network
//
//  Copyright © 2021 Secure Mobile Networking Lab (SEEMOO)
//  Copyright © 2021 The Open Wireless Link Project
//
//  SPDX-License-Identifier: AGPL-3.0-only

import SwiftUI

struct OFFetchReportsMainView: View {

    @Environment(\.findMyController) var findMyController

    @State var targetedDrop: Bool = false
    @State var error: Error?
    @State var showMap = false
    @State var loading = false

    @State var searchPartyToken: Data?
    @State var searchPartyTokenString: String = ""
    @State var keyPlistFile: Data?

    @State var showTokenPrompt = false

    var dropView: some View {
        ZStack(alignment: .center) {
            HStack {
                Spacer()
                Spacer()
            }

            VStack {
                Spacer()
                Text("Drop exported keys here")
                    .font(Font.system(size: 44, weight: .bold, design: .default))
                    .padding()

                Text("The keys can be exported into the right format using the Read FindMy Keys App.")
                    .font(.body)
                    .multilineTextAlignment(.center)
                    .padding()

                Spacer()
            }
        }
        .background(
            RoundedRectangle(cornerRadius: 20.0)
                .stroke(Color.gray, style: StrokeStyle(lineWidth: 5.0, lineCap: .round, lineJoin: .round, miterLimit: 10, dash: [15]))
            )
        .padding()
        .onDrop(of: ["public.file-url"], isTargeted: self.$targetedDrop) { (droppedData) -> Bool in
            return self.droppedData(data: droppedData)
        }

    }

    var loadingView: some View {
        VStack {
            Text("Downloading locations and decrypting...")
            .font(Font.system(size: 44, weight: .bold, design: .default))
            .padding()
        }
    }

    /// This view is shown if the search party token cannot be accessed from keychain
    var missingSearchPartyTokenView: some View {
        VStack {
            Text("Search Party token could not be fetched")
            Text("Please paste the search party token below after copying it from the macOS Keychain.")
            Text("The item that contains the key can be found by searching for: ")
            Text("com.apple.account.DeviceLocator.search-party-token")
                .font(.system(Font.TextStyle.body, design: Font.Design.monospaced))

            TextField("Search Party Token", text: self.$searchPartyTokenString)

            Button(action: {
                if !self.searchPartyTokenString.isEmpty,
                    let file = self.keyPlistFile,
                    let searchPartyToken = self.searchPartyTokenString.data(using: .utf8) {
                    self.searchPartyToken = searchPartyToken
                    self.downloadAndDecryptLocations(with: file, searchPartyToken: searchPartyToken)
                }
            }, label: {
                Text("Download reports")
            })
        }
    }

    var mapView: some View {
        ZStack {
            MapView()
            VStack {
                HStack {
                     Spacer()
                    Button(action: {
                        self.showMap = false
                        self.showTokenPrompt = false
                    }, label: {
                        Text("Import other tokens")
                    })

                     Button(action: {
                         self.exportDecryptedLocations()

                     }, label: {
                         Text("Export")
                     })

                 }
                .padding()
                Spacer()
            }

        }
    }

    var body: some View {
        GeometryReader { geo in
            if self.loading {
                self.loadingView
            } else if self.showMap {
                self.mapView
            } else if self.showTokenPrompt {
                self.missingSearchPartyTokenView
            } else {
                self.dropView
                .frame(width: geo.size.width, height: geo.size.height)
            }
        }

    }

    // swiftlint:disable identifier_name
    func droppedData(data: [NSItemProvider]) -> Bool {
        guard let itemProvider = data.first else {return false}

        itemProvider.loadItem(forTypeIdentifier: "public.file-url", options: nil) { (u, _) in
            guard let urlData = u as? Data,
                let fileURL = URL(dataRepresentation: urlData, relativeTo: nil),
                // Only plist supported
                fileURL.pathExtension == "plist",
                // Load the file
                let file = try? Data(contentsOf: fileURL)
                else {return}

            print("Received data \(fileURL)")

            self.keyPlistFile = file
            let reportsFetcher = ReportsFetcher()
            self.searchPartyToken = reportsFetcher.fetchSearchpartyToken()

            if let searchPartyToken = self.searchPartyToken {
                self.downloadAndDecryptLocations(with: file, searchPartyToken: searchPartyToken)
            } else {
                self.showTokenPrompt = true
            }

        }
        return true
    }

    func downloadAndDecryptLocations(with keyFile: Data, searchPartyToken: Data) {
        self.loading = true

        self.findMyController.loadPrivateKeys(from: keyFile, with: searchPartyToken, completion: { error in
            // Check if an error occurred
            guard error == nil else {
                self.error = error
                return
            }

            // Show map view
            self.loading = false
            self.showMap = true

        })
    }

    func exportDecryptedLocations() {
        do {
            let devices = self.findMyController.devices
            let deviceData = try PropertyListEncoder().encode(devices)

            SavePanel().saveFile(file: deviceData, fileExtension: "plist")

        } catch {
            print("Error: \(error)")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        OFFetchReportsMainView()
    }
}
