use axum::extract::FromRequestParts;

use crate::error::{ApigateCoreError, ApigateError};
use http::header::{HeaderName, HeaderValue};
use http::request::Parts;

pub struct PartsCtx<'a> {
    service: &'static str,
    route_path: &'static str,
    parts: &'a mut Parts,
}

impl<'a> PartsCtx<'a> {
    pub fn new(service: &'static str, route_path: &'static str, parts: &'a mut Parts) -> Self {
        Self {
            service,
            route_path,
            parts,
        }
    }

    pub fn service(&self) -> &'static str {
        self.service
    }

    pub fn route_path(&self) -> &'static str {
        self.route_path
    }

    pub fn method(&self) -> &http::Method {
        &self.parts.method
    }

    pub fn uri(&self) -> &http::Uri {
        &self.parts.uri
    }

    pub fn uri_mut(&mut self) -> &mut http::Uri {
        &mut self.parts.uri
    }

    pub fn headers(&self) -> &http::HeaderMap {
        &self.parts.headers
    }

    pub fn headers_mut(&mut self) -> &mut http::HeaderMap {
        &mut self.parts.headers
    }

    pub fn header(&self, name: &str) -> Option<&str> {
        self.parts.headers.get(name).and_then(|v| v.to_str().ok())
    }

    pub fn set_header(
        &mut self,
        name: impl TryInto<HeaderName>,
        value: impl TryInto<HeaderValue>,
    ) -> Result<(), ApigateError> {
        let name = name
            .try_into()
            .map_err(|_| ApigateError::from(ApigateCoreError::InvalidHeaderName))?;
        let value = value
            .try_into()
            .map_err(|_| ApigateError::from(ApigateCoreError::InvalidHeaderValue))?;

        self.parts.headers.insert(name, value);
        Ok(())
    }

    pub fn set_header_if_absent(
        &mut self,
        name: impl TryInto<HeaderName>,
        value: impl TryInto<HeaderValue>,
    ) -> Result<(), ApigateError> {
        let name = name
            .try_into()
            .map_err(|_| ApigateError::from(ApigateCoreError::InvalidHeaderName))?;
        if self.parts.headers.contains_key(&name) {
            return Ok(());
        }

        let value = value
            .try_into()
            .map_err(|_| ApigateError::from(ApigateCoreError::InvalidHeaderValue))?;

        self.parts.headers.insert(name, value);
        Ok(())
    }

    pub fn remove_header(&mut self, name: &str) {
        self.parts.headers.remove(name);
    }

    pub fn extensions(&self) -> &http::Extensions {
        &self.parts.extensions
    }

    pub fn extensions_mut(&mut self) -> &mut http::Extensions {
        &mut self.parts.extensions
    }

    pub async fn extract_path<T>(&mut self) -> Result<T, ApigateError>
    where
        T: Send,
        axum::extract::Path<T>: FromRequestParts<()>,
    {
        axum::extract::Path::<T>::from_request_parts(self.parts, &())
            .await
            .map(|p| p.0)
            .map_err(|_| ApigateError::from(ApigateCoreError::InvalidPathParameters))
    }
}
