// Copyright 2023 Salesforce, Inc. All rights reserved.
use pdk_core::logger;

use crate::error::CorsError;
use crate::model::cors::CorsResourceTypeEnum;
use crate::model::request::request_selector::RequestSelector;
use crate::model::resource::cors_resource::CorsRequestType;
use crate::model::resource::protected_resource::ResponseType;
use crate::{HeaderMap, HeaderValue};

pub(crate) const METHOD_HEADER: &str = ":method";

///
/// Processes a request according to the type of [CorsResource] defined in the configuration
/// and the [CorsRequestType] defined by properties of the incoming request.
///
pub struct RequestProcessor<'a> {
    cors: CorsResourceTypeEnum<'a>,
    request_selector: RequestSelector,
}

impl RequestProcessor<'_> {
    pub(crate) fn new(cors_type: CorsResourceTypeEnum<'_>) -> RequestProcessor {
        RequestProcessor {
            cors: cors_type,
            request_selector: RequestSelector::default(),
        }
    }

    pub fn process_request(&self, headers: &HeaderMap) -> Result<ResponseType, CorsError> {
        let request_method = self.get_method(headers);

        match request_method {
            None => Err(CorsError::RequestMethodNotFound),
            Some(method) => {
                logger::debug!("Processing incoming request [headers = {headers:?}]");

                let incoming_request = self.request_selector.get_type(headers, method);
                incoming_request.access_resource(&self.cors)
            }
        }
    }

    fn get_method<'a>(&self, headers: &'a HeaderMap<'a>) -> Option<&'a HeaderValue> {
        headers
            .iter()
            .find_map(|(k, v)| (*k == METHOD_HEADER).then_some(*v))
    }
}

#[cfg(test)]
mod request_processor_tests {
    use crate::model::cors::CorsResourceTypeEnum;
    use crate::model::request::request_processor::{RequestProcessor, METHOD_HEADER};
    use crate::model::resource::public_resource::PublicResource;

    #[test]
    fn if_no_method_is_found_then_error_is_returned() {
        let request_processor = request_processor();

        assert!(request_processor.process_request(&[]).is_err())
    }

    #[test]
    fn if_method_is_found_request_is_processed() {
        let request_processor = request_processor();

        let headers = [(METHOD_HEADER, "get")];

        assert!(request_processor.process_request(&headers).is_ok())
    }

    fn request_processor<'a>() -> RequestProcessor<'a> {
        RequestProcessor::new(CorsResourceTypeEnum::PublicResource(PublicResource::new(
            false,
        )))
    }
}
