import Foundation

enum SDLRuntimeEnvironmentCommand {
    case start(completion: @Sendable (Error?) -> Void)
    case stop(completion: @Sendable () -> Void)
}

final class SDLRuntimeEnvironment {
    private enum State {
        case idle
        case running
    }
    
    private var state: State = .idle
    private var contextActor: SDLContextActor?

    private var config: SDLConfiguration
    private let rsaCipher: CCRSACipher
    private let provider: PacketTunnelProvider

    private let commandStream: AsyncStream<SDLRuntimeEnvironmentCommand>
    private let commandCont: AsyncStream<SDLRuntimeEnvironmentCommand>.Continuation

    private var commandTask: Task<Void, Never>?

    init(config: SDLConfiguration, rsaCipher: CCRSACipher, provider: PacketTunnelProvider) {
        self.config = config
        self.rsaCipher = rsaCipher
        self.provider = provider

        let pair = AsyncStream.makeStream(of: SDLRuntimeEnvironmentCommand.self)
        self.commandStream = pair.stream
        self.commandCont = pair.continuation
    }

    func submitCommand(command: SDLRuntimeEnvironmentCommand) {
        self.commandCont.yield(command)
    }

    func getContextActor() -> SDLContextActor? {
        self.contextActor
    }

    func run() {
        let stream = self.commandStream

        self.commandTask = Task { [weak self] in
            for await command in stream {
                guard let self else {
                    break
                }

                await self.handle(command)
            }
        }
    }

    private func handle(_ command: SDLRuntimeEnvironmentCommand) async {
        switch command {
        case .start(let handler):
            do {
                try await self.startCommand()
                handler(nil)
            } catch {
                handler(error)
            }

        case .stop(let handler):
            await self.stopCommand()
            handler()
        }
    }

    private func startCommand() async throws {
        switch self.state {
        case .idle:
            SDLTunnelAppNotifier.shared.clear()

            let contextActor = SDLContextActor(
                provider: provider,
                config: config,
                rsaCipher: self.rsaCipher
            )

            self.contextActor = contextActor
            await contextActor.start()
            self.state = .running
        case .running:
            SDLLogger.log("[SDLRuntimeEnvironment] is running, ignore start command")
        }
    }

    private func stopCommand() async {
        switch self.state {
        case .idle:
            SDLLogger.log("[SDLRuntimeEnvironment] is idle, ignore stop command")

        case .running:
            let contextActor = self.contextActor
            self.contextActor = nil
            self.state = .idle

            await contextActor?.stop()
        }
    }

    deinit {
        self.commandCont.finish()
        self.commandTask?.cancel()
        self.commandTask = nil
    }
}
