import NIO
import NIOIMAPCore
@preconcurrency import NIOIMAP
@preconcurrency import NIOSSL

actor IMAPConnection {
	private let host: String
	private let port: Int
	private let group: EventLoopGroup
	private var channel: Channel?
	private let responseHandler: IMAPResponseHandler

	init(host: String, port: Int) {
		self.host = host
		self.port = port
		self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
		self.responseHandler = IMAPResponseHandler()
	}

	func connect() async throws {
		let sslContext = try NIOSSLContext(configuration: TLSConfiguration.makeClientConfiguration())
		let handler = responseHandler
		let hostname = host

		let bootstrap = ClientBootstrap(group: group)
			.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
			.channelInitializer { channel in
				let sslHandler = try! NIOSSLClientHandler(context: sslContext, serverHostname: hostname)
				return channel.pipeline.addHandlers([
					sslHandler,
					IMAPClientHandler(),
					handler,
				])
			}

		channel = try await bootstrap.connect(host: host, port: port).get()
		try await handler.waitForGreeting()
	}

	func sendCommand(_ tag: String, command: CommandStreamPart) async throws -> [Response] {
		guard let channel else { throw IMAPError.notConnected }
		let handler = responseHandler
		return try await withCheckedThrowingContinuation { continuation in
			handler.sendCommand(tag: tag, continuation: continuation)
			channel.writeAndFlush(IMAPClientHandler.Message.part(command), promise: nil)
		}
	}

	/// Send multiple command parts as a single multi-part command (e.g. APPEND).
	/// The tag must match the tag embedded in the first part.
	func sendMultiPartCommand(_ tag: String, parts: [CommandStreamPart]) async throws -> [Response] {
		guard let channel else { throw IMAPError.notConnected }
		let handler = responseHandler
		return try await withCheckedThrowingContinuation { continuation in
			handler.sendCommand(tag: tag, continuation: continuation)
			for part in parts {
				channel.writeAndFlush(IMAPClientHandler.Message.part(part), promise: nil)
			}
		}
	}

	func disconnect() async throws {
		try await channel?.close()
		channel = nil
	}

	func shutdown() async throws {
		try await group.shutdownGracefully()
	}
}
