import NIO

/// NIO handler that parses SMTP line-based responses.
/// SMTP response format: `<3-digit code><separator><text>\r\n`
/// Separator `-` means continuation, space means final line.
final class SMTPResponseHandler: ChannelInboundHandler, @unchecked Sendable {
	typealias InboundIn = ByteBuffer

	private var stringBuffer = ""
	private var responseLines: [String] = []
	private var responseCode: Int?
	private var continuation: CheckedContinuation<SMTPResponse, Error>?
	private var greetingContinuation: CheckedContinuation<SMTPResponse, Error>?

	func channelRead(context: ChannelHandlerContext, data: NIOAny) {
		var buffer = unwrapInboundIn(data)
		guard let received = buffer.readString(length: buffer.readableBytes) else { return }
		stringBuffer.append(received)

		while let lineEnd = stringBuffer.range(of: "\r\n") {
			let line = String(stringBuffer[stringBuffer.startIndex..<lineEnd.lowerBound])
			stringBuffer.removeSubrange(stringBuffer.startIndex..<lineEnd.upperBound)
			processLine(line)
		}
	}

	private func processLine(_ line: String) {
		guard line.count >= 3,
			  let code = Int(line.prefix(3))
		else { return }

		// Extract text after the code + separator character
		let text: String
		if line.count > 4 {
			text = String(line.dropFirst(4))
		} else {
			text = ""
		}

		responseLines.append(text)

		if responseCode == nil {
			responseCode = code
		}

		// Separator is at index 3: `-` for continuation, space for final
		let isFinal: Bool
		if line.count > 3 {
			isFinal = line[line.index(line.startIndex, offsetBy: 3)] == " "
		} else {
			isFinal = true
		}

		if isFinal {
			let response = SMTPResponse(code: code, lines: responseLines)
			responseLines = []
			responseCode = nil

			if let cont = greetingContinuation {
				greetingContinuation = nil
				cont.resume(returning: response)
			} else if let cont = continuation {
				continuation = nil
				cont.resume(returning: response)
			}
		}
	}

	func errorCaught(context: ChannelHandlerContext, error: Error) {
		if let cont = continuation {
			continuation = nil
			cont.resume(throwing: error)
		}
		if let cont = greetingContinuation {
			greetingContinuation = nil
			cont.resume(throwing: error)
		}
		context.close(promise: nil)
	}

	func waitForGreeting() async throws -> SMTPResponse {
		try await withCheckedThrowingContinuation { cont in
			greetingContinuation = cont
		}
	}

	func waitForResponse() async throws -> SMTPResponse {
		try await withCheckedThrowingContinuation { cont in
			continuation = cont
		}
	}
}
