import NIOCore

public extension MutagenDaemon {
    func refreshSessions() async {
        guard case .running = state else { return }
        let sessions: Synchronization_ListResponse
        do {
            sessions = try await client!.sync.list(Synchronization_ListRequest.with { req in
                req.selection = .with { selection in
                    selection.all = true
                }
            })
        } catch {
            state = .failed(.grpcFailure(error))
            return
        }
        sessionState = sessions.sessionStates.map { FileSyncSession(state: $0) }
    }

    func createSession(
        localPath: String,
        agentHost: String,
        remotePath: String
    ) async throws(DaemonError) {
        if case .stopped = state {
            do throws(DaemonError) {
                try await start()
            } catch {
                state = .failed(error)
                throw error
            }
        }
        let (stream, promptID) = try await host()
        defer { stream.cancel() }
        let req = Synchronization_CreateRequest.with { req in
            req.prompter = promptID
            req.specification = .with { spec in
                spec.alpha = .with { alpha in
                    alpha.protocol = .local
                    alpha.path = localPath
                }
                spec.beta = .with { beta in
                    beta.protocol = .ssh
                    beta.host = agentHost
                    beta.path = remotePath
                }
                // TODO: Ingest a config from somewhere
                spec.configuration = Synchronization_Configuration()
                spec.configurationAlpha = Synchronization_Configuration()
                spec.configurationBeta = Synchronization_Configuration()
            }
        }
        do {
            // The first creation will need to transfer the agent binary
            // TODO: Because this is pretty long, we should show progress updates
            // using the prompter messages
            _ = try await client!.sync.create(req, callOptions: .init(timeLimit: .timeout(sessionMgmtReqTimeout * 4)))
        } catch {
            throw .grpcFailure(error)
        }
        await refreshSessions()
    }

    func deleteSessions(ids: [String]) async throws(DaemonError) {
        // Terminating sessions does not require prompting, according to the
        // Mutagen CLI
        let (stream, promptID) = try await host(allowPrompts: false)
        defer { stream.cancel() }
        guard case .running = state else { return }
        do {
            _ = try await client!.sync.terminate(Synchronization_TerminateRequest.with { req in
                req.prompter = promptID
                req.selection = .with { selection in
                    selection.specifications = ids
                }
            }, callOptions: .init(timeLimit: .timeout(sessionMgmtReqTimeout)))
        } catch {
            throw .grpcFailure(error)
        }
        await refreshSessions()
    }

    func pauseSessions(ids: [String]) async throws(DaemonError) {
        // Pausing sessions does not require prompting, according to the
        // Mutagen CLI
        let (stream, promptID) = try await host(allowPrompts: false)
        defer { stream.cancel() }
        guard case .running = state else { return }
        do {
            _ = try await client!.sync.pause(Synchronization_PauseRequest.with { req in
                req.prompter = promptID
                req.selection = .with { selection in
                    selection.specifications = ids
                }
            }, callOptions: .init(timeLimit: .timeout(sessionMgmtReqTimeout)))
        } catch {
            throw .grpcFailure(error)
        }
        await refreshSessions()
    }

    func resumeSessions(ids: [String]) async throws(DaemonError) {
        // Resuming sessions does not require prompting, according to the
        // Mutagen CLI
        let (stream, promptID) = try await host(allowPrompts: false)
        defer { stream.cancel() }
        guard case .running = state else { return }
        do {
            _ = try await client!.sync.resume(Synchronization_ResumeRequest.with { req in
                req.prompter = promptID
                req.selection = .with { selection in
                    selection.specifications = ids
                }
            }, callOptions: .init(timeLimit: .timeout(sessionMgmtReqTimeout)))
        } catch {
            throw .grpcFailure(error)
        }
        await refreshSessions()
    }
}