import Foundation
import NIO
@preconcurrency import NIOSSL
import Models

actor SMTPConnection {
	private let host: String
	private let port: Int
	private let security: SMTPSecurity
	private let group: EventLoopGroup
	private var channel: Channel?
	private let responseHandler: SMTPResponseHandler

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

	func connect() async throws -> SMTPResponse {
		let handler = responseHandler
		let hostname = host

		let bootstrap: ClientBootstrap
		if security == .ssl {
			let sslContext = try NIOSSLContext(configuration: TLSConfiguration.makeClientConfiguration())
			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, handler])
				}
		} else {
			// STARTTLS: start plain, upgrade later
			bootstrap = ClientBootstrap(group: group)
				.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
				.channelInitializer { channel in
					channel.pipeline.addHandler(handler)
				}
		}

		do {
			channel = try await bootstrap.connect(host: host, port: port).get()
		} catch {
			throw SMTPError.connectionFailed(error.localizedDescription)
		}

		return try await handler.waitForGreeting()
	}

	func sendCommand(_ command: String) async throws -> SMTPResponse {
		guard let channel else { throw SMTPError.notConnected }
		let handler = responseHandler

		var buffer = channel.allocator.buffer(capacity: command.utf8.count + 2)
		buffer.writeString(command + "\r\n")
		channel.writeAndFlush(buffer, promise: nil)

		return try await handler.waitForResponse()
	}

	func upgradeToTLS() async throws {
		guard let channel else { throw SMTPError.notConnected }
		let sslContext = try NIOSSLContext(configuration: TLSConfiguration.makeClientConfiguration())
		let sslHandler = try NIOSSLClientHandler(context: sslContext, serverHostname: host)
		do {
			try await channel.pipeline.addHandler(sslHandler, position: .first).get()
		} catch {
			throw SMTPError.tlsUpgradeFailed
		}
	}

	func sendRawBytes(_ data: Data) async throws {
		guard let channel else { throw SMTPError.notConnected }
		var buffer = channel.allocator.buffer(capacity: data.count)
		buffer.writeBytes(data)
		channel.writeAndFlush(buffer, promise: nil)
	}

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

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