use bytes::Bytes;
use http_body_util::{BodyExt, Full};
use hyper::header::{self, HeaderName, HeaderValue};
use hyper::{Request, Response, StatusCode};

use super::request::canonical_u64_header;
use super::{BoxError, HttpResponse};
use crate::config::WebCarrier;
use crate::web::frame;

/// Validates and resolves the optional carrier lane header.
pub(super) fn carrier_lane<B>(request: &Request<B>, carrier: WebCarrier) -> Option<Option<u32>> {
    match carrier {
        WebCarrier::Https => (!request.headers().contains_key("x-lane-id")).then_some(None),
        WebCarrier::HttpsLanes => canonical_u64_header(request, "x-lane-id")
            .and_then(|value| u32::try_from(value).ok())
            .filter(|value| *value <= frame::MAX_STREAM_ID)
            .map(Some),
        WebCarrier::Websocket | WebCarrier::WebsocketLanes => None,
    }
}

/// Applies common binary carrier response headers.
pub(super) fn carrier_headers(response: &mut HttpResponse) {
    response.headers_mut().insert(
        header::CONTENT_TYPE,
        HeaderValue::from_static("application/octet-stream"),
    );
    response
        .headers_mut()
        .insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
}

/// Builds an empty no-store carrier response.
pub(super) fn carrier_empty(status: StatusCode) -> HttpResponse {
    let mut response = empty_response(status);
    response
        .headers_mut()
        .insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
    response
}

/// Builds the bounded retryable carrier saturation response.
pub(super) fn service_unavailable() -> HttpResponse {
    let mut response = carrier_empty(StatusCode::SERVICE_UNAVAILABLE);
    response
        .headers_mut()
        .insert(header::RETRY_AFTER, HeaderValue::from_static("1"));
    response
}

/// Builds the ordinary decoy upstream failure response.
pub(super) fn bad_gateway() -> HttpResponse {
    full_response(
        StatusCode::BAD_GATEWAY,
        Bytes::from_static(b"site unavailable\n"),
    )
}

/// Builds the ordinary unmatched-host response.
pub(super) fn generic_not_found() -> HttpResponse {
    full_response(StatusCode::NOT_FOUND, Bytes::from_static(b"not found\n"))
}

/// Builds one in-memory response with an exact content length.
pub(super) fn full_response(status: StatusCode, body: Bytes) -> HttpResponse {
    let length = body.len();
    let body = Full::new(body)
        .map_err(|never| -> BoxError { match never {} })
        .boxed_unsync();
    let mut response = Response::new(body);
    *response.status_mut() = status;
    insert_header(&mut response, header::CONTENT_LENGTH, &length.to_string());
    response
}

fn empty_response(status: StatusCode) -> HttpResponse {
    full_response(status, Bytes::new())
}

/// Inserts one validated dynamic response header value.
pub(super) fn insert_header(response: &mut HttpResponse, name: HeaderName, value: &str) {
    if let Ok(value) = HeaderValue::from_str(value) {
        response.headers_mut().insert(name, value);
    }
}
