// Copyright 2023 Salesforce, Inc. All rights reserved.
use crate::{
    configuration::Configuration,
    error::CorsError,
    model::{
        cors::CorsResourceFactory, request::request_processor::RequestProcessor,
        resource::protected_resource::ResponseType as InnerResponseType,
    },
};

/// Represents the CORS response variants.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum ResponseType {
    /// Preflight CORS response.
    Preflight,

    /// Main CORS response.
    Main,
}

/// Output type for [`CorsChecker::check_headers`].
#[derive(Debug, Clone)]
pub struct Check {
    response_type: ResponseType,
    headers: Vec<(String, String)>,
}

impl Check {
    fn from_response_type(response_type: InnerResponseType) -> Self {
        match response_type {
            InnerResponseType::Main(headers) => Self {
                response_type: ResponseType::Main,
                headers,
            },
            InnerResponseType::Preflight(headers) => Self {
                response_type: ResponseType::Preflight,
                headers,
            },
        }
    }

    /// Retuns the checked CORS response type.
    pub fn response_type(&self) -> ResponseType {
        self.response_type
    }

    /// Returns the checked headers.
    pub fn headers(&self) -> &[(String, String)] {
        &self.headers
    }

    /// Consumes this check and returns the internal list checked of headers.
    pub fn into_headers(self) -> Vec<(String, String)> {
        self.headers
    }
}

/// CORS checker.
pub struct Cors<'a> {
    processor: RequestProcessor<'a>,
}

impl<'a> Cors<'a> {
    /// Creates a new CORS checker.
    pub fn new(config: &'a Configuration<'a>) -> Self {
        Cors {
            processor: RequestProcessor::new(CorsResourceFactory::from_configuration(config)),
        }
    }

    /// Performs a CORS checking of request headers.
    pub fn check_headers(&self, headers: &[(String, String)]) -> Result<Check, CorsError> {
        let header_map: Vec<_> = headers
            .iter()
            .map(|(n, v)| (n.as_str(), v.as_str()))
            .collect();

        self.processor
            .process_request(header_map.as_slice())
            .map(Check::from_response_type)
    }
}
