use std::{fmt::Debug, mem};

use bytes::BytesMut;
use conduwuit::{Err, Result, debug_error, err, utils, warn};
use reqwest::Client;
use ruma::api::{IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken};

/// Sends a request to an antispam service
pub(crate) async fn send_antispam_request<T>(
	client: &Client,
	base_url: &str,
	secret: &str,
	request: T,
) -> Result<T::IncomingResponse>
where
	T: OutgoingRequest + Debug + Send,
{
	const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_15];
	let http_request = request
		.try_into_http_request::<BytesMut>(base_url, SendAccessToken::Always(secret), &VERSIONS)?
		.map(BytesMut::freeze);
	let reqwest_request = reqwest::Request::try_from(http_request)?;

	let mut response = client.execute(reqwest_request).await.map_err(|e| {
		warn!("Could not send request to antispam: {e:?}");
		e
	})?;

	// reqwest::Response -> http::Response conversion
	let status = response.status();
	let mut http_response_builder = http::Response::builder()
		.status(status)
		.version(response.version());
	mem::swap(
		response.headers_mut(),
		http_response_builder
			.headers_mut()
			.expect("http::response::Builder is usable"),
	);

	let body = response.bytes().await?; // TODO: handle timeout

	if !status.is_success() {
		debug_error!("Antispam response bytes: {:?}", utils::string_from_bytes(&body));
		return match status {
			| http::StatusCode::FORBIDDEN =>
				Err!(Request(Forbidden("Request was rejected by antispam service.",))),
			| _ => Err!(BadServerResponse(warn!(
				"Antispam returned unsuccessful HTTP response {status}",
			))),
		};
	}

	let response = T::IncomingResponse::try_from_http_response(
		http_response_builder
			.body(body)
			.expect("reqwest body is valid http body"),
	);

	response.map_err(|e| {
		err!(BadServerResponse(warn!(
			"Antispam returned invalid/malformed response bytes: {e}",
		)))
	})
}
