use axum::{
    extract::FromRequestParts,
    http::{
        header::{ACCEPT, CONTENT_TYPE, HeaderMap},
        request::Parts,
    },
};
use serde::{Deserialize, Serialize};

use crate::errors::RecorderError as Error;

#[derive(Debug, Deserialize, Serialize)]
pub struct Format(pub RespondTo);

#[derive(Debug, Deserialize, Serialize)]
pub enum RespondTo {
    None,
    Html,
    Json,
    Xml,
    Other(String),
}

fn detect_format(content_type: &str) -> RespondTo {
    if content_type.starts_with("application/json") {
        RespondTo::Json
    } else if content_type.starts_with("text/html") {
        RespondTo::Html
    } else if content_type.starts_with("text/xml")
        || content_type.starts_with("application/xml")
        || content_type.starts_with("application/xhtml")
    {
        RespondTo::Xml
    } else {
        RespondTo::Other(content_type.to_string())
    }
}

pub fn get_respond_to(headers: &HeaderMap) -> RespondTo {
    #[allow(clippy::option_if_let_else)]
    if let Some(content_type) = headers.get(CONTENT_TYPE).and_then(|h| h.to_str().ok()) {
        detect_format(content_type)
    } else if let Some(content_type) = headers.get(ACCEPT).and_then(|h| h.to_str().ok()) {
        detect_format(content_type)
    } else {
        RespondTo::None
    }
}

impl<S> FromRequestParts<S> for Format
where
    S: Send + Sync,
{
    type Rejection = Error;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Error> {
        Ok(Self(get_respond_to(&parts.headers)))
    }
}

impl<S> FromRequestParts<S> for RespondTo
where
    S: Send + Sync,
{
    type Rejection = Error;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Error> {
        Ok(get_respond_to(&parts.headers))
    }
}
