import SwiftUI
import CryptoKit

let server = Client(server: URL(string: "https://christophhagen.de/sesame/")!)

struct ContentView: View {

    @AppStorage("counter")
    var nextMessageCounter: Int = 0

    @State
    var keyManager = KeyManagement()

    let history = HistoryManager()
    
    @State
    var state: ClientState = .noKeyAvailable

    @State
    private var timer: Timer?

    @State
    private var hasActiveRequest = false

    @State
    private var responseTime: Date? = nil

    @State
    private var showKeySheet = false

    @State
    private var showHistorySheet = false

    var isPerformingRequests: Bool {
        hasActiveRequest ||
        state == .waitingForResponse
    }

    var buttonBackground: Color {
        state.allowsAction ?
            .white.opacity(0.2) :
            .black.opacity(0.2)
    }

    let buttonBorderWidth: CGFloat = 3

    var buttonColor: Color {
        state.allowsAction ? .white : .gray
    }

    private let buttonWidth: CGFloat = 250

    private let smallButtonHeight: CGFloat = 50

    private let smallButtonWidth: CGFloat = 120

    private let smallButtonBorderWidth: CGFloat = 1

    var body: some View {
        GeometryReader { geo in
            VStack(spacing: 20) {
                HStack {
                    Button("History", action: { showHistorySheet = true })
                        .frame(width: smallButtonWidth,
                               height: smallButtonHeight)
                        .background(.white.opacity(0.2))
                        .cornerRadius(smallButtonHeight / 2)
                        .overlay(RoundedRectangle(cornerRadius: smallButtonHeight / 2).stroke(lineWidth: smallButtonBorderWidth).foregroundColor(.white))
                        .foregroundColor(.white)
                        .font(.title2)
                        .padding()
                    Spacer()
                    Button("Keys", action: { showKeySheet = true })
                        .frame(width: smallButtonWidth,
                               height: smallButtonHeight)
                        .background(.white.opacity(0.2))
                        .cornerRadius(smallButtonHeight / 2)
                        .overlay(RoundedRectangle(cornerRadius: smallButtonHeight / 2).stroke(lineWidth: smallButtonBorderWidth).foregroundColor(.white))
                        .foregroundColor(.white)
                        .font(.title2)
                        .padding()
                }
                Spacer()
                if state.requiresDescription {
                    Text(state.description)
                        .padding()
                }
                Button(state.actionText, action: mainButtonPressed)
                    .frame(width: buttonWidth,
                           height: buttonWidth)
                    .background(buttonBackground)
                    .cornerRadius(buttonWidth / 2)
                    .overlay(RoundedRectangle(cornerRadius: buttonWidth / 2).stroke(lineWidth: buttonBorderWidth).foregroundColor(buttonColor))
                    .foregroundColor(buttonColor)
                    .font(.title)
                    .disabled(!state.allowsAction)
                    .padding(.bottom, (geo.size.width-buttonWidth) / 2)
            }
            .background(state.color)
            .onAppear {
                if keyManager.hasAllKeys {
                    state = .requestingStatus
                }
                startRegularStatusUpdates()
            }
            .onDisappear {
                endRegularStatusUpdates()
            }
            .frame(width: geo.size.width, height: geo.size.height)
            .animation(.easeInOut, value: state.color)
            .sheet(isPresented: $showKeySheet) {
                KeyView(keyManager: $keyManager)
            }
            .sheet(isPresented: $showHistorySheet) {
                HistoryView(manager: history)
            }
        }
        .preferredColorScheme(.dark)
    }
    
    func mainButtonPressed() {
        guard let key = keyManager.get(.remoteKey),
              let token = keyManager.get(.authToken)?.data else {
            return
        }

        let count = UInt32(nextMessageCounter)
        let sentTime = Date()
        let content = Message.Content(
            time: sentTime.timestamp,
            id: count)
        let message = content.authenticate(using: key)
        let historyItem = HistoryItem(sent: message, date: sentTime)
        state = .waitingForResponse
        print("Sending message \(count)")
        Task {
            let (newState, message) = await server.send(message, authToken: token)
            let receivedTime = Date.now
            responseTime = receivedTime
            state = newState
            let finishedItem = historyItem.didReceive(response: newState, date: receivedTime, message: message)
            print("Interval: \(receivedTime.timeIntervalSince(sentTime))", "\(finishedItem.roundTripTime ?? -1)")
            process(item: finishedItem)
        }
    }

    private func process(item: HistoryItem) {
        guard let message = item.incomingMessage else {
            save(historyItem: item)
            return
        }

        guard let key = keyManager.get(.deviceKey) else {
            save(historyItem: item.notAuthenticated())
            return
        }
        guard message.isValid(using: key) else {
            save(historyItem: item.invalidated())
            return
        }
        nextMessageCounter = Int(message.content.id)
        save(historyItem: item)
    }

    private func save(historyItem: HistoryItem) {
        do {
            try history.save(item: historyItem)
        } catch {
            print("Failed to save item: \(error)")
        }
    }


    private func startRegularStatusUpdates() {
        guard timer == nil else {
            return
        }
        DispatchQueue.main.async {
            timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: checkDeviceStatus)
            timer!.fire()
        }
    }

    private func endRegularStatusUpdates() {
        timer?.invalidate()
        timer = nil
    }
    
    func checkDeviceStatus(_ timer: Timer) {
        guard let authToken = keyManager.get(.authToken) else {
            return
        }
        guard !hasActiveRequest else {
            return
        }
        hasActiveRequest = true
        Task {
            let newState = await server.deviceStatus(authToken: authToken.data)
            hasActiveRequest = false
            switch state {
            case .noKeyAvailable:
                return
            case .requestingStatus, .deviceNotAvailable, .ready:
                state = newState
            case .waitingForResponse:
                return
            case .messageRejected, .openSesame, .internalError, .responseRejected:
                guard let time = responseTime else {
                    state = newState
                    return
                }
                responseTime = nil
                // Wait at least 5 seconds after these states have been reached before changing the
                // interface to allow sufficient time to see the result
                let elapsed = Date.now.timeIntervalSince(time)
                guard elapsed < 5 else {
                    state = newState
                    return
                }
                let secondsToWait = Int(elapsed.rounded(.up))
                DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(secondsToWait)) {
                    state = newState
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewDevice("iPhone 8")
    }
}

extension Date {

    var timestamp: UInt32 {
        UInt32(timeIntervalSince1970.rounded())
    }

    init(timestamp: UInt32) {
        self.init(timeIntervalSince1970: TimeInterval(timestamp))
    }
}
