// Copyright 2023 Salesforce, Inc. All rights reserved.
use crate::policy_context::authentication::{Authentication, AuthenticationHandler};
use classy::extract::context::FilterContext;
use classy::extract::{Extract, FromContext};
use classy::hl::context::{RequestContext, ResponseContext};
use classy::hl::{HeadersHandler, HttpClientResponse, StreamProperties};
use pdk_script::{
    EvaluationError, Evaluator, HandlerAttributesBinding, IntoValue, PayloadBinding, Script, Value,
};
use std::convert::Infallible;

pub struct DefaultBindings {
    stream: StreamProperties,
    auth: Authentication,
}

impl FromContext<FilterContext> for DefaultBindings {
    type Error = Infallible;

    fn from_context(context: &FilterContext) -> Result<Self, Self::Error> {
        let stream = context.extract()?;
        let auth = context.extract()?;
        Ok(Self { stream, auth })
    }
}

impl FromContext<RequestContext> for DefaultBindings {
    type Error = Infallible;

    fn from_context(context: &RequestContext) -> Result<Self, Self::Error> {
        let stream = context.extract()?;
        let auth = context.extract()?;
        Ok(Self { stream, auth })
    }
}

impl<C> FromContext<ResponseContext<C>> for DefaultBindings {
    type Error = Infallible;

    fn from_context(context: &ResponseContext<C>) -> Result<Self, Self::Error> {
        let stream = context.extract()?;
        let auth = context.extract()?;
        Ok(Self { stream, auth })
    }
}

impl DefaultBindings {
    pub fn evaluator<'a>(&'a self, script: &'a Script) -> DefaultEvaluator<'a> {
        DefaultEvaluator {
            provider: self,
            evaluator: script.evaluator(),
        }
    }
}

pub struct DefaultEvaluator<'a> {
    provider: &'a DefaultBindings,
    evaluator: Evaluator<'a>,
}

impl DefaultEvaluator<'_> {
    pub fn bind_vars<K: IntoValue>(&mut self, name: &str, value: K) {
        self.evaluator.bind_vars(name, value)
    }

    pub fn bind_headers(&mut self, binding: &dyn HeadersHandler) {
        let binding = HandlerAttributesBinding::new(binding, &self.provider.stream);
        self.evaluator.bind_attributes(&binding)
    }

    pub fn bind_payload(&mut self, binding: &dyn PayloadBinding) {
        self.evaluator.bind_payload(binding);
    }

    pub fn bind_http_client_response(&mut self, binding: &HttpClientResponse) {
        self.evaluator.bind_attributes(binding);
        self.evaluator.bind_payload(binding);
    }

    pub fn bind_authentication(&mut self) {
        let auth = self.provider.auth.authentication().unwrap_or_default();
        self.evaluator.bind_authentication(&auth)
    }

    pub fn is_ready(&self) -> bool {
        self.evaluator.is_ready()
    }

    pub fn eval(self) -> Result<Value, EvaluationError> {
        self.evaluator.eval()
    }
}
