// File: crates/hyperswitch_connectors/src/connectors/recurly.rs
// Module: hyperswitch_connectors
// Public functions: 1
// Public structs: 1

pub mod transformers;

use base64::Engine;
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
use common_utils::request::{Method, Request, RequestBuilder};
use common_utils::{consts, errors::CustomResult, ext_traits::BytesExt};
#[cfg(feature = "v1")]
use error_stack::report;
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    router_data::{ConnectorAuthType, ErrorResponse},
    router_data_v2::{
        flow_common_types::{
            GetSubscriptionEstimateData, GetSubscriptionPlanPricesData, GetSubscriptionPlansData,
            SubscriptionCreateData, SubscriptionCustomerData,
        },
        UasFlowData,
    },
    router_flow_types::{
        subscriptions::{
            GetSubscriptionEstimate, GetSubscriptionPlanPrices, GetSubscriptionPlans,
            SubscriptionCreate,
        },
        unified_authentication_service::{
            Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate,
        },
        CreateConnectorCustomer,
    },
    router_request_types::{
        subscriptions::{
            GetSubscriptionEstimateRequest, GetSubscriptionPlanPricesRequest,
            GetSubscriptionPlansRequest, SubscriptionCreateRequest,
        },
        unified_authentication_service::{
            UasAuthenticationRequestData, UasAuthenticationResponseData,
            UasConfirmationRequestData, UasPostAuthenticationRequestData,
            UasPreAuthenticationRequestData,
        },
        ConnectorCustomerData,
    },
    router_response_types::{
        subscriptions::{
            GetSubscriptionEstimateResponse, GetSubscriptionPlanPricesResponse,
            GetSubscriptionPlansResponse, SubscriptionCreateResponse,
        },
        PaymentsResponseData,
    },
};
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
use hyperswitch_domain_models::{
    router_data_v2::flow_common_types as recovery_flow_common_types,
    router_flow_types::revenue_recovery as recovery_router_flows,
    router_request_types::revenue_recovery as recovery_request_types,
    router_response_types::revenue_recovery as recovery_response_types,
    types as recovery_router_data_types,
};
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
use hyperswitch_interfaces::types;
use hyperswitch_interfaces::{
    api::{self, ConnectorCommon, ConnectorSpecifications, ConnectorValidation},
    configs::Connectors,
    connector_integration_v2::ConnectorIntegrationV2,
    errors,
    events::connector_api_logs::ConnectorEvent,
    types::Response,
    webhooks,
};
use masking::{Mask, PeekInterface};
use transformers as recurly;

use crate::{connectors::recurly::transformers::RecurlyWebhookBody, constants::headers};
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
use crate::{
    connectors::recurly::transformers::{RecurlyRecordStatus, RecurlyRecoveryDetailsData},
    types::ResponseRouterDataV2,
};
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
const STATUS_SUCCESSFUL_ENDPOINT: &str = "mark_successful";
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
const STATUS_FAILED_ENDPOINT: &str = "mark_failed";

const RECURLY_API_VERSION: &str = "application/vnd.recurly.v2021-02-25";

// We don't need an amount converter because we are not using it anywhere in code, but it's important to note that Float Major Unit is the standard format used by Recurly.
#[derive(Clone)]
pub struct Recurly {
    // amount_converter: &'static (dyn AmountConvertor<Output = FloatMajorUnit> + Sync),
}

impl Recurly {
    pub fn new() -> &'static Self {
        &Self {}
    }

    fn get_signature_elements_from_header(
        headers: &actix_web::http::header::HeaderMap,
    ) -> CustomResult<Vec<Vec<u8>>, errors::ConnectorError> {
        let security_header = headers
            .get("recurly-signature")
            .ok_or(errors::ConnectorError::WebhookSignatureNotFound)?;
        let security_header_str = security_header
            .to_str()
            .change_context(errors::ConnectorError::WebhookSignatureNotFound)?;
        let header_parts: Vec<Vec<u8>> = security_header_str
            .split(',')
            .map(|part| part.trim().as_bytes().to_vec())
            .collect();

        Ok(header_parts)
    }
}

impl api::PayoutsV2 for Recurly {}
impl api::UnifiedAuthenticationServiceV2 for Recurly {}
impl api::UasPreAuthenticationV2 for Recurly {}
impl api::UasPostAuthenticationV2 for Recurly {}
impl api::UasAuthenticationV2 for Recurly {}
impl api::UasAuthenticationConfirmationV2 for Recurly {}
impl
    ConnectorIntegrationV2<
        PreAuthenticate,
        UasFlowData,
        UasPreAuthenticationRequestData,
        UasAuthenticationResponseData,
    > for Recurly
{
    //TODO: implement sessions flow
}

impl
    ConnectorIntegrationV2<
        PostAuthenticate,
        UasFlowData,
        UasPostAuthenticationRequestData,
        UasAuthenticationResponseData,
    > for Recurly
{
    //TODO: implement sessions flow
}
impl
    ConnectorIntegrationV2<
        AuthenticationConfirmation,
        UasFlowData,
        UasConfirmationRequestData,
        UasAuthenticationResponseData,
    > for Recurly
{
    //TODO: implement sessions flow
}
impl
    ConnectorIntegrationV2<
        Authenticate,
        UasFlowData,
        UasAuthenticationRequestData,
        UasAuthenticationResponseData,
    > for Recurly
{
    //TODO: implement sessions flow
}

impl api::revenue_recovery_v2::RevenueRecoveryV2 for Recurly {}
impl api::subscriptions_v2::SubscriptionsV2 for Recurly {}
impl api::subscriptions_v2::GetSubscriptionPlansV2 for Recurly {}
impl api::subscriptions_v2::SubscriptionConnectorCustomerV2 for Recurly {}

impl
    ConnectorIntegrationV2<
        GetSubscriptionPlans,
        GetSubscriptionPlansData,
        GetSubscriptionPlansRequest,
        GetSubscriptionPlansResponse,
    > for Recurly
{
}

impl
    ConnectorIntegrationV2<
        CreateConnectorCustomer,
        SubscriptionCustomerData,
        ConnectorCustomerData,
        PaymentsResponseData,
    > for Recurly
{
}

impl api::subscriptions_v2::GetSubscriptionPlanPricesV2 for Recurly {}

impl
    ConnectorIntegrationV2<
        GetSubscriptionPlanPrices,
        GetSubscriptionPlanPricesData,
        GetSubscriptionPlanPricesRequest,
        GetSubscriptionPlanPricesResponse,
    > for Recurly
{
}
impl api::subscriptions_v2::SubscriptionsCreateV2 for Recurly {}
impl
    ConnectorIntegrationV2<
        SubscriptionCreate,
        SubscriptionCreateData,
        SubscriptionCreateRequest,
        SubscriptionCreateResponse,
    > for Recurly
{
}

impl api::subscriptions_v2::GetSubscriptionEstimateV2 for Recurly {}
impl
    ConnectorIntegrationV2<
        GetSubscriptionEstimate,
        GetSubscriptionEstimateData,
        GetSubscriptionEstimateRequest,
        GetSubscriptionEstimateResponse,
    > for Recurly
{
}

#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl api::revenue_recovery_v2::RevenueRecoveryRecordBackV2 for Recurly {}
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl api::revenue_recovery_v2::BillingConnectorPaymentsSyncIntegrationV2 for Recurly {}
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl api::revenue_recovery_v2::BillingConnectorInvoiceSyncIntegrationV2 for Recurly {}

impl ConnectorCommon for Recurly {
    fn id(&self) -> &'static str {
        "recurly"
    }

    fn get_currency_unit(&self) -> api::CurrencyUnit {
        api::CurrencyUnit::Minor
        //    TODO! Check connector documentation, on which unit they are processing the currency.
        //    If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor,
        //    if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base
    }

    fn common_get_content_type(&self) -> &'static str {
        "application/json"
    }

    fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
        connectors.recurly.base_url.as_ref()
    }

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let auth = recurly::RecurlyAuthType::try_from(auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        Ok(vec![
            (
                headers::AUTHORIZATION.to_string(),
                format!(
                    "Basic {}",
                    consts::BASE64_ENGINE.encode(auth.api_key.peek())
                )
                .into_masked(),
            ),
            (
                headers::ACCEPT.to_string(),
                RECURLY_API_VERSION.to_string().into_masked(),
            ),
        ])
    }

    fn build_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        let response: recurly::RecurlyErrorResponse = res
            .response
            .parse_struct("RecurlyErrorResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: response.code,
            message: response.message,
            reason: response.reason,
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}

impl ConnectorValidation for Recurly {
    //TODO: implement functions when support enabled
}

#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl
    ConnectorIntegrationV2<
        recovery_router_flows::BillingConnectorPaymentsSync,
        recovery_flow_common_types::BillingConnectorPaymentsSyncFlowData,
        recovery_request_types::BillingConnectorPaymentsSyncRequest,
        recovery_response_types::BillingConnectorPaymentsSyncResponse,
    > for Recurly
{
    fn get_headers(
        &self,
        req: &recovery_router_data_types::BillingConnectorPaymentsSyncRouterDataV2,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let mut header = vec![(
            headers::CONTENT_TYPE.to_string(),
            self.common_get_content_type().to_string().into(),
        )];
        let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
        header.append(&mut api_key);
        Ok(header)
    }

    fn get_url(
        &self,
        req: &recovery_router_data_types::BillingConnectorPaymentsSyncRouterDataV2,
    ) -> CustomResult<String, errors::ConnectorError> {
        let transaction_uuid = &req.request.billing_connector_psync_id;
        Ok(format!(
            "{}/transactions/uuid-{transaction_uuid}",
            req.request.connector_params.base_url,
        ))
    }

    fn build_request_v2(
        &self,
        req: &recovery_router_data_types::BillingConnectorPaymentsSyncRouterDataV2,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Get)
            .url(&types::BillingConnectorPaymentsSyncTypeV2::get_url(
                self, req,
            )?)
            .attach_default_headers()
            .headers(types::BillingConnectorPaymentsSyncTypeV2::get_headers(
                self, req,
            )?)
            .build();
        Ok(Some(request))
    }

    fn handle_response_v2(
        &self,
        data: &recovery_router_data_types::BillingConnectorPaymentsSyncRouterDataV2,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<
        recovery_router_data_types::BillingConnectorPaymentsSyncRouterDataV2,
        errors::ConnectorError,
    > {
        let response: RecurlyRecoveryDetailsData = res
            .response
            .parse_struct::<RecurlyRecoveryDetailsData>("RecurlyRecoveryDetailsData")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        recovery_router_data_types::BillingConnectorPaymentsSyncRouterDataV2::try_from(
            ResponseRouterDataV2 {
                response,
                data: data.clone(),
                http_code: res.status_code,
            },
        )
    }

    fn get_error_response_v2(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl
    ConnectorIntegrationV2<
        recovery_router_flows::InvoiceRecordBack,
        recovery_flow_common_types::InvoiceRecordBackData,
        recovery_request_types::InvoiceRecordBackRequest,
        recovery_response_types::InvoiceRecordBackResponse,
    > for Recurly
{
    fn get_headers(
        &self,
        req: &recovery_router_data_types::InvoiceRecordBackRouterDataV2,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let mut header = vec![(
            headers::CONTENT_TYPE.to_string(),
            self.common_get_content_type().to_string().into(),
        )];
        let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
        header.append(&mut api_key);
        Ok(header)
    }

    fn get_url(
        &self,
        req: &recovery_router_data_types::InvoiceRecordBackRouterDataV2,
    ) -> CustomResult<String, errors::ConnectorError> {
        let invoice_id = req
            .request
            .merchant_reference_id
            .get_string_repr()
            .to_string();

        let status = RecurlyRecordStatus::try_from(req.request.attempt_status)?;

        let status_endpoint = match status {
            RecurlyRecordStatus::Success => STATUS_SUCCESSFUL_ENDPOINT,
            RecurlyRecordStatus::Failure => STATUS_FAILED_ENDPOINT,
        };

        Ok(format!(
            "{}/invoices/{invoice_id}/{status_endpoint}",
            req.request.connector_params.base_url,
        ))
    }

    fn build_request_v2(
        &self,
        req: &recovery_router_data_types::InvoiceRecordBackRouterDataV2,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Put)
                .url(&types::InvoiceRecordBackTypeV2::get_url(self, req)?)
                .attach_default_headers()
                .headers(types::InvoiceRecordBackTypeV2::get_headers(self, req)?)
                .header("Content-Length", "0")
                .build(),
        ))
    }

    fn handle_response_v2(
        &self,
        data: &recovery_router_data_types::InvoiceRecordBackRouterDataV2,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<
        recovery_router_data_types::InvoiceRecordBackRouterDataV2,
        errors::ConnectorError,
    > {
        let response: recurly::RecurlyRecordBackResponse = res
            .response
            .parse_struct("recurly RecurlyRecordBackResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        recovery_router_data_types::InvoiceRecordBackRouterDataV2::try_from(ResponseRouterDataV2 {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response_v2(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl
    ConnectorIntegrationV2<
        recovery_router_flows::BillingConnectorInvoiceSync,
        recovery_flow_common_types::BillingConnectorInvoiceSyncFlowData,
        recovery_request_types::BillingConnectorInvoiceSyncRequest,
        recovery_response_types::BillingConnectorInvoiceSyncResponse,
    > for Recurly
{
    fn get_headers(
        &self,
        req: &recovery_router_data_types::BillingConnectorInvoiceSyncRouterDataV2,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let mut header = vec![(
            headers::CONTENT_TYPE.to_string(),
            self.common_get_content_type().to_string().into(),
        )];
        let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
        header.append(&mut api_key);
        Ok(header)
    }

    fn get_url(
        &self,
        req: &recovery_router_data_types::BillingConnectorInvoiceSyncRouterDataV2,
    ) -> CustomResult<String, errors::ConnectorError> {
        let invoice_id = &req.request.billing_connector_invoice_id;
        Ok(format!(
            "{}/invoices/{invoice_id}",
            req.request.connector_params.base_url,
        ))
    }

    fn build_request_v2(
        &self,
        req: &recovery_router_data_types::BillingConnectorInvoiceSyncRouterDataV2,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Get)
            .url(&types::BillingConnectorInvoiceSyncTypeV2::get_url(
                self, req,
            )?)
            .attach_default_headers()
            .headers(types::BillingConnectorInvoiceSyncTypeV2::get_headers(
                self, req,
            )?)
            .build();
        Ok(Some(request))
    }

    fn handle_response_v2(
        &self,
        data: &recovery_router_data_types::BillingConnectorInvoiceSyncRouterDataV2,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<
        recovery_router_data_types::BillingConnectorInvoiceSyncRouterDataV2,
        errors::ConnectorError,
    > {
        let response: recurly::RecurlyInvoiceSyncResponse = res
            .response
            .parse_struct::<recurly::RecurlyInvoiceSyncResponse>("RecurlyInvoiceSyncResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        recovery_router_data_types::BillingConnectorInvoiceSyncRouterDataV2::try_from(
            ResponseRouterDataV2 {
                response,
                data: data.clone(),
                http_code: res.status_code,
            },
        )
    }

    fn get_error_response_v2(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[async_trait::async_trait]
impl webhooks::IncomingWebhook for Recurly {
    fn get_webhook_source_verification_algorithm(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn common_utils::crypto::VerifySignature + Send>, errors::ConnectorError>
    {
        Ok(Box::new(common_utils::crypto::HmacSha256))
    }

    fn get_webhook_source_verification_signature(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
        _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
        // The `recurly-signature` header consists of a Unix timestamp (in milliseconds) followed by one or more HMAC-SHA256 signatures, separated by commas.
        // Multiple signatures exist when a secret key is regenerated, with the old key remaining active for 24 hours.
        let header_values = Self::get_signature_elements_from_header(request.headers)?;
        let signature = header_values
            .get(1)
            .ok_or(errors::ConnectorError::WebhookSignatureNotFound)?;
        hex::decode(signature).change_context(errors::ConnectorError::WebhookSignatureNotFound)
    }

    fn get_webhook_source_verification_message(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
        _merchant_id: &common_utils::id_type::MerchantId,
        _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
        let header_values = Self::get_signature_elements_from_header(request.headers)?;
        let timestamp = header_values
            .first()
            .ok_or(errors::ConnectorError::WebhookSignatureNotFound)?;
        Ok(format!(
            "{}.{}",
            String::from_utf8_lossy(timestamp),
            String::from_utf8_lossy(request.body)
        )
        .into_bytes())
    }
    #[cfg(all(feature = "revenue_recovery", feature = "v2"))]
    fn get_webhook_object_reference_id(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        let webhook = RecurlyWebhookBody::get_webhook_object_from_body(request.body)
            .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
        Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
            api_models::payments::PaymentIdType::ConnectorTransactionId(webhook.uuid),
        ))
    }

    #[cfg(any(feature = "v1", not(all(feature = "revenue_recovery", feature = "v2"))))]
    fn get_webhook_object_reference_id(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    #[cfg(all(feature = "revenue_recovery", feature = "v2"))]
    fn get_webhook_event_type(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
        let webhook = RecurlyWebhookBody::get_webhook_object_from_body(request.body)
            .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
        let event = match webhook.event_type {
            transformers::RecurlyPaymentEventType::PaymentSucceeded => {
                api_models::webhooks::IncomingWebhookEvent::RecoveryPaymentSuccess
            }
            transformers::RecurlyPaymentEventType::PaymentFailed => {
                api_models::webhooks::IncomingWebhookEvent::RecoveryPaymentFailure
            }
        };
        Ok(event)
    }

    #[cfg(any(feature = "v1", not(all(feature = "revenue_recovery", feature = "v2"))))]
    fn get_webhook_event_type(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_resource_object(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        let webhook = RecurlyWebhookBody::get_webhook_object_from_body(request.body)
            .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
        Ok(Box::new(webhook))
    }
}

impl ConnectorSpecifications for Recurly {}


// Function: validate
// File: crates/scheduler/src/configs/validations.rs
// Module: scheduler

pub fn validate(&self) -> Result<(), ApplicationError>


// File: crates/analytics/src/payment_intents/metrics/payments_success_rate.rs
// Module: analytics

use std::collections::HashSet;

use api_models::analytics::{
    payment_intents::{
        PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetricsBucketIdentifier,
    },
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use super::PaymentIntentMetricRow;
use crate::{
    enums::AuthInfo,
    query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
};

#[derive(Default)]
pub(super) struct PaymentsSuccessRate;

#[async_trait::async_trait]
impl<T> super::PaymentIntentMetric<T> for PaymentsSuccessRate
where
    T: AnalyticsDataSource + super::PaymentIntentMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[PaymentIntentDimensions],
        auth: &AuthInfo,
        filters: &PaymentIntentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(PaymentIntentMetricsBucketIdentifier, PaymentIntentMetricRow)>>
    {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::PaymentIntent);

        let mut dimensions = dimensions.to_vec();

        dimensions.push(PaymentIntentDimensions::PaymentIntentStatus);

        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }
        query_builder
            .add_select_column(Aggregate::Count {
                field: None,
                alias: Some("count"),
            })
            .switch()?;

        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;

        filters.set_filter_clause(&mut query_builder).switch()?;

        auth.set_filter_clause(&mut query_builder).switch()?;

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        for dim in dimensions.iter() {
            query_builder
                .add_group_by_clause(dim)
                .attach_printable("Error grouping by dimensions")
                .switch()?;
        }

        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .execute_query::<PaymentIntentMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    PaymentIntentMetricsBucketIdentifier::new(
                        None,
                        i.currency.as_ref().map(|i| i.0),
                        i.profile_id.clone(),
                        i.connector.clone(),
                        i.authentication_type.as_ref().map(|i| i.0),
                        i.payment_method.clone(),
                        i.payment_method_type.clone(),
                        i.card_network.clone(),
                        i.merchant_id.clone(),
                        i.card_last_4.clone(),
                        i.card_issuer.clone(),
                        i.error_reason.clone(),
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(PaymentIntentMetricsBucketIdentifier, PaymentIntentMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// Implementation: impl api::Payment for for Airwallex
// File: crates/hyperswitch_connectors/src/connectors/airwallex.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::Payment for for Airwallex


// File: crates/hyperswitch_connectors/src/connectors/zen.rs
// Module: hyperswitch_connectors
// Public structs: 1

pub mod transformers;
use std::{fmt::Debug, sync::LazyLock};

use api_models::webhooks::IncomingWebhookEvent;
use common_enums::{enums, CallConnectorAction, PaymentAction};
use common_utils::{
    crypto,
    errors::CustomResult,
    ext_traits::{ByteSliceExt, BytesExt},
    request::{Method, Request, RequestBuilder, RequestContent},
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    api::ApplicationResponse,
    payment_method_data::PaymentMethodData,
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{
        access_token_auth::AccessTokenAuth,
        payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void},
        refunds::{Execute, RSync},
    },
    router_request_types::{
        AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
        PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData,
        RefundsData, SetupMandateRequestData,
    },
    router_response_types::{
        ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData,
        SupportedPaymentMethods, SupportedPaymentMethodsExt,
    },
    types::{
        PaymentsAuthorizeRouterData, PaymentsSyncRouterData, RefundSyncRouterData,
        RefundsRouterData,
    },
};
use hyperswitch_interfaces::{
    api::{
        self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse,
        ConnectorSpecifications, ConnectorValidation,
    },
    configs::Connectors,
    consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE},
    errors,
    events::connector_api_logs::ConnectorEvent,
    types::{PaymentsAuthorizeType, PaymentsSyncType, RefundExecuteType, RefundSyncType, Response},
    webhooks::{IncomingWebhook, IncomingWebhookFlowError, IncomingWebhookRequestDetails},
};
use masking::{Mask, PeekInterface, Secret};
use transformers::{self as zen, ZenPaymentStatus, ZenWebhookTxnType};
use uuid::Uuid;

use crate::{constants::headers, types::ResponseRouterData};

#[derive(Debug, Clone)]
pub struct Zen;

impl api::Payment for Zen {}
impl api::PaymentSession for Zen {}
impl api::ConnectorAccessToken for Zen {}
impl api::MandateSetup for Zen {}
impl api::PaymentAuthorize for Zen {}
impl api::PaymentSync for Zen {}
impl api::PaymentCapture for Zen {}
impl api::PaymentVoid for Zen {}
impl api::PaymentToken for Zen {}
impl api::Refund for Zen {}
impl api::RefundExecute for Zen {}
impl api::RefundSync for Zen {}

impl Zen {
    fn get_default_header() -> (String, masking::Maskable<String>) {
        ("request-id".to_string(), Uuid::new_v4().to_string().into())
    }
}

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Zen
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    fn build_headers(
        &self,
        req: &RouterData<Flow, Request, Response>,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let mut headers = vec![(
            headers::CONTENT_TYPE.to_string(),
            self.get_content_type().to_string().into(),
        )];

        let mut auth_header = self.get_auth_header(&req.connector_auth_type)?;
        headers.append(&mut auth_header);

        Ok(headers)
    }
}

impl ConnectorCommon for Zen {
    fn id(&self) -> &'static str {
        "zen"
    }

    fn get_currency_unit(&self) -> api::CurrencyUnit {
        api::CurrencyUnit::Base
    }

    fn common_get_content_type(&self) -> &'static str {
        mime::APPLICATION_JSON.essence_str()
    }

    fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
        connectors.zen.base_url.as_ref()
    }

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let auth = zen::ZenAuthType::try_from(auth_type)?;
        Ok(vec![(
            headers::AUTHORIZATION.to_string(),
            format!("Bearer {}", auth.api_key.peek()).into_masked(),
        )])
    }

    fn build_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        let response: zen::ZenErrorResponse = res
            .response
            .parse_struct("Zen ErrorResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_error_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: response
                .error
                .clone()
                .map_or(NO_ERROR_CODE.to_string(), |error| error.code),
            message: response.error.map_or_else(
                || response.message.unwrap_or(NO_ERROR_MESSAGE.to_string()),
                |error| error.message,
            ),
            reason: None,
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}

impl ConnectorValidation for Zen {
    fn validate_psync_reference_id(
        &self,
        _data: &PaymentsSyncData,
        _is_three_ds: bool,
        _status: enums::AttemptStatus,
        _connector_meta_data: Option<common_utils::pii::SecretSerdeValue>,
    ) -> CustomResult<(), errors::ConnectorError> {
        // since we can make psync call with our reference_id, having connector_transaction_id is not an mandatory criteria
        Ok(())
    }
}

impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Zen {
    //TODO: implement sessions flow
}

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
    for Zen
{
    // Not Implemented (R)
}

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken> for Zen {}

impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData> for Zen {
    fn build_request(
        &self,
        _req: &RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>,
        _connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Err(errors::ConnectorError::NotImplemented("Setup Mandate flow for Zen".to_string()).into())
    }
}

impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> for Zen {
    fn get_headers(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let mut headers = self.build_headers(req, connectors)?;
        let api_headers = match req.request.payment_method_data {
            PaymentMethodData::Wallet(_) => None,
            _ => Some(Self::get_default_header()),
        };
        if let Some(api_header) = api_headers {
            headers.push(api_header)
        }
        Ok(headers)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let endpoint = match &req.request.payment_method_data {
            PaymentMethodData::Wallet(_) => {
                let base_url = connectors
                    .zen
                    .secondary_base_url
                    .as_ref()
                    .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?;
                format!("{base_url}api/checkouts")
            }
            _ => format!("{}v1/transactions", self.base_url(connectors)),
        };
        Ok(endpoint)
    }

    fn get_request_body(
        &self,
        req: &PaymentsAuthorizeRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_router_data = zen::ZenRouterData::try_from((
            &self.get_currency_unit(),
            req.request.currency,
            req.request.amount,
            req,
        ))?;
        let connector_req = zen::ZenPaymentsRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?)
                .set_body(PaymentsAuthorizeType::get_request_body(
                    self, req, connectors,
                )?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &PaymentsAuthorizeRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsAuthorizeRouterData, errors::ConnectorError> {
        let response: zen::ZenPaymentsResponse = res
            .response
            .parse_struct("Zen PaymentsAuthorizeResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Zen {
    fn get_headers(
        &self,
        req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let mut headers = self.build_headers(req, connectors)?;
        headers.push(Self::get_default_header());
        Ok(headers)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}v1/transactions/merchant/{}",
            self.base_url(connectors),
            req.attempt_id,
        ))
    }

    fn build_request(
        &self,
        req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Get)
                .url(&PaymentsSyncType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(PaymentsSyncType::get_headers(self, req, connectors)?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &PaymentsSyncRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsSyncRouterData, errors::ConnectorError> {
        let response: zen::ZenPaymentsResponse = res
            .response
            .parse_struct("zen PaymentsSyncResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> for Zen {
    fn build_request(
        &self,
        _req: &RouterData<Capture, PaymentsCaptureData, PaymentsResponseData>,
        _connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Err(errors::ConnectorError::FlowNotSupported {
            flow: "Capture".to_owned(),
            connector: "Zen".to_owned(),
        }
        .into())
    }
}

impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Zen {
    fn build_request(
        &self,
        _req: &RouterData<Void, PaymentsCancelData, PaymentsResponseData>,
        _connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Err(errors::ConnectorError::FlowNotSupported {
            flow: "Void".to_owned(),
            connector: "Zen".to_owned(),
        }
        .into())
    }
}

impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Zen {
    fn get_headers(
        &self,
        req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let mut headers = self.build_headers(req, connectors)?;
        headers.push(Self::get_default_header());
        Ok(headers)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        _req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}v1/transactions/refund",
            self.base_url(connectors),
        ))
    }

    fn get_request_body(
        &self,
        req: &RefundsRouterData<Execute>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_router_data = zen::ZenRouterData::try_from((
            &self.get_currency_unit(),
            req.request.currency,
            req.request.refund_amount,
            req,
        ))?;
        let connector_req = zen::ZenRefundRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&RefundExecuteType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(RefundExecuteType::get_headers(self, req, connectors)?)
            .set_body(RefundExecuteType::get_request_body(self, req, connectors)?)
            .build();
        Ok(Some(request))
    }

    fn handle_response(
        &self,
        data: &RefundsRouterData<Execute>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<RefundsRouterData<Execute>, errors::ConnectorError> {
        let response: zen::RefundResponse = res
            .response
            .parse_struct("zen RefundResponse")
            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Zen {
    fn get_headers(
        &self,
        req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let mut headers = self.build_headers(req, connectors)?;
        headers.push(Self::get_default_header());
        Ok(headers)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}v1/transactions/merchant/{}",
            self.base_url(connectors),
            req.request.refund_id
        ))
    }

    fn build_request(
        &self,
        req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Get)
                .url(&RefundSyncType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(RefundSyncType::get_headers(self, req, connectors)?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &RefundSyncRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<RefundSyncRouterData, errors::ConnectorError> {
        let response: zen::RefundResponse = res
            .response
            .parse_struct("zen RefundSyncResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[async_trait::async_trait]
impl IncomingWebhook for Zen {
    fn get_webhook_source_verification_algorithm(
        &self,
        _request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
        Ok(Box::new(crypto::Sha256))
    }

    fn get_webhook_source_verification_signature(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
        _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
        let webhook_body: zen::ZenWebhookSignature = request
            .body
            .parse_struct("ZenWebhookSignature")
            .change_context(errors::ConnectorError::WebhookSignatureNotFound)?;
        let signature = webhook_body.hash;
        hex::decode(signature).change_context(errors::ConnectorError::WebhookSignatureNotFound)
    }

    fn get_webhook_source_verification_message(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
        _merchant_id: &common_utils::id_type::MerchantId,
        _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
        let webhook_body: zen::ZenWebhookBody = request
            .body
            .parse_struct("ZenWebhookBody")
            .change_context(errors::ConnectorError::WebhookSignatureNotFound)?;
        let msg = format!(
            "{}{}{}{}",
            webhook_body.merchant_transaction_id,
            webhook_body.currency,
            webhook_body.amount,
            webhook_body.status.to_string().to_uppercase()
        );
        Ok(msg.into_bytes())
    }

    async fn verify_webhook_source(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
        merchant_id: &common_utils::id_type::MerchantId,
        connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
        _connector_account_details: crypto::Encryptable<Secret<serde_json::Value>>,
        connector_label: &str,
    ) -> CustomResult<bool, errors::ConnectorError> {
        let algorithm = self.get_webhook_source_verification_algorithm(request)?;
        let connector_webhook_secrets = self
            .get_webhook_source_verification_merchant_secret(
                merchant_id,
                connector_label,
                connector_webhook_details,
            )
            .await?;
        let signature =
            self.get_webhook_source_verification_signature(request, &connector_webhook_secrets)?;

        let mut message = self.get_webhook_source_verification_message(
            request,
            merchant_id,
            &connector_webhook_secrets,
        )?;
        let mut secret = connector_webhook_secrets.secret;
        message.append(&mut secret);
        algorithm
            .verify_signature(&secret, &signature, &message)
            .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)
    }

    fn get_webhook_object_reference_id(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        let webhook_body: zen::ZenWebhookObjectReference = request
            .body
            .parse_struct("ZenWebhookObjectReference")
            .change_context(errors::ConnectorError::WebhookSignatureNotFound)?;
        Ok(match &webhook_body.transaction_type {
            ZenWebhookTxnType::TrtPurchase => api_models::webhooks::ObjectReferenceId::PaymentId(
                api_models::payments::PaymentIdType::PaymentAttemptId(
                    webhook_body.merchant_transaction_id,
                ),
            ),
            ZenWebhookTxnType::TrtRefund => api_models::webhooks::ObjectReferenceId::RefundId(
                api_models::webhooks::RefundIdType::RefundId(webhook_body.merchant_transaction_id),
            ),

            ZenWebhookTxnType::Unknown => Err(errors::ConnectorError::WebhookReferenceIdNotFound)?,
        })
    }

    fn get_webhook_event_type(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<IncomingWebhookEvent, errors::ConnectorError> {
        let details: zen::ZenWebhookEventType = request
            .body
            .parse_struct("ZenWebhookEventType")
            .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;

        Ok(match &details.transaction_type {
            ZenWebhookTxnType::TrtPurchase => match &details.status {
                ZenPaymentStatus::Rejected => IncomingWebhookEvent::PaymentIntentFailure,
                ZenPaymentStatus::Accepted => IncomingWebhookEvent::PaymentIntentSuccess,
                _ => Err(errors::ConnectorError::WebhookEventTypeNotFound)?,
            },
            ZenWebhookTxnType::TrtRefund => match &details.status {
                ZenPaymentStatus::Rejected => IncomingWebhookEvent::RefundFailure,
                ZenPaymentStatus::Accepted => IncomingWebhookEvent::RefundSuccess,
                _ => Err(errors::ConnectorError::WebhookEventTypeNotFound)?,
            },
            ZenWebhookTxnType::Unknown => IncomingWebhookEvent::EventNotSupported,
        })
    }

    fn get_webhook_resource_object(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        let reference_object: serde_json::Value = serde_json::from_slice(request.body)
            .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
        Ok(Box::new(reference_object))
    }
    fn get_webhook_api_response(
        &self,
        _request: &IncomingWebhookRequestDetails<'_>,
        _error_kind: Option<IncomingWebhookFlowError>,
    ) -> CustomResult<ApplicationResponse<serde_json::Value>, errors::ConnectorError> {
        Ok(ApplicationResponse::Json(serde_json::json!({
            "status": "ok"
        })))
    }
}

impl ConnectorRedirectResponse for Zen {
    fn get_flow_type(
        &self,
        _query_params: &str,
        _json_payload: Option<serde_json::Value>,
        action: PaymentAction,
    ) -> CustomResult<CallConnectorAction, errors::ConnectorError> {
        match action {
            PaymentAction::PSync
            | PaymentAction::CompleteAuthorize
            | PaymentAction::PaymentAuthenticateCompleteAuthorize => {
                Ok(CallConnectorAction::Trigger)
            }
        }
    }
}

static ZEN_SUPPORTED_PAYMENT_METHODS: LazyLock<SupportedPaymentMethods> = LazyLock::new(|| {
    let supported_capture_methods = vec![
        enums::CaptureMethod::Automatic,
        enums::CaptureMethod::Manual,
        enums::CaptureMethod::SequentialAutomatic,
    ];

    let supported_card_network = vec![
        common_enums::CardNetwork::Visa,
        common_enums::CardNetwork::Mastercard,
        common_enums::CardNetwork::AmericanExpress,
        common_enums::CardNetwork::DinersClub,
        common_enums::CardNetwork::Discover,
        common_enums::CardNetwork::Interac,
        common_enums::CardNetwork::JCB,
        common_enums::CardNetwork::CartesBancaires,
        common_enums::CardNetwork::UnionPay,
    ];

    let mut zen_supported_payment_methods = SupportedPaymentMethods::new();

    zen_supported_payment_methods.add(
        enums::PaymentMethod::Card,
        enums::PaymentMethodType::Credit,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: Some(
                api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({
                    api_models::feature_matrix::CardSpecificFeatures {
                        three_ds: common_enums::FeatureStatus::NotSupported,
                        no_three_ds: common_enums::FeatureStatus::Supported,
                        supported_card_networks: supported_card_network.clone(),
                    }
                }),
            ),
        },
    );
    zen_supported_payment_methods.add(
        enums::PaymentMethod::Card,
        enums::PaymentMethodType::Debit,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: Some(
                api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({
                    api_models::feature_matrix::CardSpecificFeatures {
                        three_ds: common_enums::FeatureStatus::NotSupported,
                        no_three_ds: common_enums::FeatureStatus::Supported,
                        supported_card_networks: supported_card_network.clone(),
                    }
                }),
            ),
        },
    );
    zen_supported_payment_methods.add(
        enums::PaymentMethod::Voucher,
        enums::PaymentMethodType::Boleto,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: None,
        },
    );
    zen_supported_payment_methods.add(
        enums::PaymentMethod::Voucher,
        enums::PaymentMethodType::Efecty,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: None,
        },
    );
    zen_supported_payment_methods.add(
        enums::PaymentMethod::Voucher,
        enums::PaymentMethodType::PagoEfectivo,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: None,
        },
    );
    zen_supported_payment_methods.add(
        enums::PaymentMethod::Voucher,
        enums::PaymentMethodType::RedCompra,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: None,
        },
    );
    zen_supported_payment_methods.add(
        enums::PaymentMethod::Voucher,
        enums::PaymentMethodType::RedPagos,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: None,
        },
    );
    zen_supported_payment_methods.add(
        enums::PaymentMethod::BankTransfer,
        enums::PaymentMethodType::Multibanco,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: None,
        },
    );
    zen_supported_payment_methods.add(
        enums::PaymentMethod::BankTransfer,
        enums::PaymentMethodType::Pix,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: None,
        },
    );
    zen_supported_payment_methods.add(
        enums::PaymentMethod::BankTransfer,
        enums::PaymentMethodType::Pse,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: None,
        },
    );
    zen_supported_payment_methods.add(
        enums::PaymentMethod::Wallet,
        enums::PaymentMethodType::ApplePay,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: None,
        },
    );
    zen_supported_payment_methods.add(
        enums::PaymentMethod::Wallet,
        enums::PaymentMethodType::GooglePay,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: None,
        },
    );

    zen_supported_payment_methods
});

static ZEN_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
        display_name: "Zen",
        description: "Zen Payment Gateway is a secure and scalable payment solution that enables businesses to accept online payments globally with various methods and currencies.",
        connector_type: enums::HyperswitchConnectorCategory::PaymentGateway,
        integration_status: enums::ConnectorIntegrationStatus::Live,
    };

static ZEN_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 2] =
    [enums::EventClass::Payments, enums::EventClass::Refunds];

impl ConnectorSpecifications for Zen {
    fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {
        Some(&ZEN_CONNECTOR_INFO)
    }

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        Some(&*ZEN_SUPPORTED_PAYMENT_METHODS)
    }

    fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> {
        Some(&ZEN_SUPPORTED_WEBHOOK_FLOWS)
    }
}


// Function: pre_payment_tokenization
// File: crates/router/src/core/payments/tokenization.rs
// Module: router

pub fn pre_payment_tokenization(
    state: &SessionState,
    customer_id: id_type::CustomerId,
    card: &payment_method_data::Card,
) -> RouterResult<(Option<pm_types::TokenResponse>, Option<String>)>


// Function: update_straight_through_routing
// File: crates/router/src/core/payments.rs
// Module: router

pub fn update_straight_through_routing<F, D>(
    payment_data: &mut D,
    request_straight_through: serde_json::Value,
) -> CustomResult<(), errors::ParsingError>
where
    F: Send + Clone,
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,


// Function: contract_based_dynamic_routing_setup
// File: crates/router/src/core/routing.rs
// Module: router

pub fn contract_based_dynamic_routing_setup(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: common_utils::id_type::ProfileId,
    feature_to_enable: routing_types::DynamicRoutingFeatures,
    config: Option<routing_types::ContractBasedRoutingConfig>,
) -> RouterResult<service_api::ApplicationResponse<routing_types::RoutingDictionaryRecord>>


// File: crates/openapi/src/routes/platform.rs
// Module: openapi
// Public functions: 1

#[cfg(feature = "v1")]
/// Platform - Create
///
/// Create a new platform account
#[utoipa::path(
    post,
    path = "/user/create_platform",
    request_body(
        content = PlatformAccountCreateRequest,
        description = "Create a platform account with organization_name",
        examples(
            (
                "Create a platform account with organization_name" = (
                    value = json!({"organization_name": "organization_abc"})
                )
            )
        )
    ),
    responses(
        (
            status = 200,
            description = "Platform Account Created",
            body = PlatformAccountCreateResponse,
            examples(
                (
                    "Successful Platform Account Creation" = (
                        description = "Return values for a successfully created platform account",
                        value = json!({
                            "org_id": "org_abc",
                            "org_name": "organization_abc",
                            "org_type": "platform",
                            "merchant_id": "merchant_abc",
                            "merchant_account_type": "platform"
                        })
                    )
                )
            )
        ),
        (status = 400, description = "Invalid data")
    ),
    tag = "Platform",
    operation_id = "Create a Platform Account",
    security(("jwt_key" = []))
)]
pub async fn create_platform_account() {}


// Implementation: impl PaymentRedirectFlow for for PaymentRedirectSync
// File: crates/router/src/core/payments.rs
// Module: router
// Methods: 3 total (0 public)

impl PaymentRedirectFlow for for PaymentRedirectSync


// Implementation: impl api::revenue_recovery_v2::RevenueRecoveryV2 for for Recurly
// File: crates/hyperswitch_connectors/src/connectors/recurly.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::revenue_recovery_v2::RevenueRecoveryV2 for for Recurly


// Implementation: impl scheduler::SchedulerSessionState for for SessionState
// File: crates/router/src/routes/app.rs
// Module: router
// Methods: 1 total (0 public)

impl scheduler::SchedulerSessionState for for SessionState


// Implementation: impl api::MandateSetup for for Katapult
// File: crates/hyperswitch_connectors/src/connectors/katapult.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::MandateSetup for for Katapult


// Struct: ChargebeeRefundRequest
// File: crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ChargebeeRefundRequest


// Function: create_key_in_key_manager
// File: crates/common_utils/src/keymanager.rs
// Module: common_utils

pub fn create_key_in_key_manager(
    state: &KeyManagerState,
    request_body: EncryptionCreateRequest,
) -> errors::CustomResult<DataKeyCreateResponse, errors::KeyManagerError>


// Struct: ChargebeeRecordPaymentRequest
// File: crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ChargebeeRecordPaymentRequest


// Struct: UpdateScorePayload
// File: crates/api_models/src/open_router.rs
// Module: api_models
// Implementations: 0

pub struct UpdateScorePayload


// Struct: GrpcRecoveryHeaders
// File: crates/external_services/src/grpc_client/revenue_recovery.rs
// Module: external_services
// Implementations: 0

pub struct GrpcRecoveryHeaders


// File: crates/router/src/core/api_keys.rs
// Module: router
// Public functions: 14
// Public structs: 2

use common_utils::date_time;
#[cfg(feature = "email")]
use diesel_models::{api_keys::ApiKey, enums as storage_enums};
use error_stack::{report, ResultExt};
use masking::{PeekInterface, StrongSecret};
use router_env::{instrument, tracing};

use crate::{
    configs::settings,
    consts,
    core::errors::{self, RouterResponse, StorageErrorExt},
    db::domain,
    routes::{metrics, SessionState},
    services::{authentication, ApplicationResponse},
    types::{api, storage, transformers::ForeignInto},
};

#[cfg(feature = "email")]
const API_KEY_EXPIRY_TAG: &str = "API_KEY";
#[cfg(feature = "email")]
const API_KEY_EXPIRY_NAME: &str = "API_KEY_EXPIRY";
#[cfg(feature = "email")]
const API_KEY_EXPIRY_RUNNER: diesel_models::ProcessTrackerRunner =
    diesel_models::ProcessTrackerRunner::ApiKeyExpiryWorkflow;

static HASH_KEY: once_cell::sync::OnceCell<StrongSecret<[u8; PlaintextApiKey::HASH_KEY_LEN]>> =
    once_cell::sync::OnceCell::new();

impl settings::ApiKeys {
    pub fn get_hash_key(
        &self,
    ) -> errors::RouterResult<&'static StrongSecret<[u8; PlaintextApiKey::HASH_KEY_LEN]>> {
        HASH_KEY.get_or_try_init(|| {
            <[u8; PlaintextApiKey::HASH_KEY_LEN]>::try_from(
                hex::decode(self.hash_key.peek())
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("API key hash key has invalid hexadecimal data")?
                    .as_slice(),
            )
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("The API hashing key has incorrect length")
            .map(StrongSecret::new)
        })
    }
}

// Defining new types `PlaintextApiKey` and `HashedApiKey` in the hopes of reducing the possibility
// of plaintext API key being stored in the data store.
pub struct PlaintextApiKey(StrongSecret<String>);

#[derive(Debug, PartialEq, Eq)]
pub struct HashedApiKey(String);

impl PlaintextApiKey {
    const HASH_KEY_LEN: usize = 32;

    const PREFIX_LEN: usize = 12;

    pub fn new(length: usize) -> Self {
        let env = router_env::env::prefix_for_env();
        let key = common_utils::crypto::generate_cryptographically_secure_random_string(length);
        Self(format!("{env}_{key}").into())
    }

    pub fn new_key_id() -> common_utils::id_type::ApiKeyId {
        let env = router_env::env::prefix_for_env();
        common_utils::id_type::ApiKeyId::generate_key_id(env)
    }

    pub fn prefix(&self) -> String {
        self.0.peek().chars().take(Self::PREFIX_LEN).collect()
    }

    pub fn peek(&self) -> &str {
        self.0.peek()
    }

    pub fn keyed_hash(&self, key: &[u8; Self::HASH_KEY_LEN]) -> HashedApiKey {
        /*
        Decisions regarding API key hashing algorithm chosen:

        - Since API key hash verification would be done for each request, there is a requirement
          for the hashing to be quick.
        - Password hashing algorithms would not be suitable for this purpose as they're designed to
          prevent brute force attacks, considering that the same password could be shared  across
          multiple sites by the user.
        - Moreover, password hash verification happens once per user session, so the delay involved
          is negligible, considering the security benefits it provides.
          While with API keys (assuming uniqueness of keys across the application), the delay
          involved in hashing (with the use of a password hashing algorithm) becomes significant,
          considering that it must be done per request.
        - Since we are the only ones generating API keys and are able to guarantee their uniqueness,
          a simple hash algorithm is sufficient for this purpose.

        Hash algorithms considered:
        - Password hashing algorithms: Argon2id and PBKDF2
        - Simple hashing algorithms: HMAC-SHA256, HMAC-SHA512, BLAKE3

        After benchmarking the simple hashing algorithms, we decided to go with the BLAKE3 keyed
        hashing algorithm, with a randomly generated key for the hash key.
        */

        HashedApiKey(
            blake3::keyed_hash(key, self.0.peek().as_bytes())
                .to_hex()
                .to_string(),
        )
    }
}

#[instrument(skip_all)]
pub async fn create_api_key(
    state: SessionState,
    api_key: api::CreateApiKeyRequest,
    key_store: domain::MerchantKeyStore,
) -> RouterResponse<api::CreateApiKeyResponse> {
    let api_key_config = state.conf.api_keys.get_inner();
    let store = state.store.as_ref();

    let merchant_id = key_store.merchant_id.clone();

    let hash_key = api_key_config.get_hash_key()?;
    let plaintext_api_key = PlaintextApiKey::new(consts::API_KEY_LENGTH);
    let api_key = storage::ApiKeyNew {
        key_id: PlaintextApiKey::new_key_id(),
        merchant_id: merchant_id.to_owned(),
        name: api_key.name,
        description: api_key.description,
        hashed_api_key: plaintext_api_key.keyed_hash(hash_key.peek()).into(),
        prefix: plaintext_api_key.prefix(),
        created_at: date_time::now(),
        expires_at: api_key.expiration.into(),
        last_used: None,
    };

    let api_key = store
        .insert_api_key(api_key)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to insert new API key")?;

    let state_inner = state.clone();
    let hashed_api_key = api_key.hashed_api_key.clone();
    let merchant_id_inner = merchant_id.clone();
    let key_id = api_key.key_id.clone();
    let expires_at = api_key.expires_at;

    authentication::decision::spawn_tracked_job(
        async move {
            authentication::decision::add_api_key(
                &state_inner,
                hashed_api_key.into_inner().into(),
                merchant_id_inner,
                key_id,
                expires_at.map(authentication::decision::convert_expiry),
            )
            .await
        },
        authentication::decision::ADD,
    );

    metrics::API_KEY_CREATED.add(
        1,
        router_env::metric_attributes!(("merchant", merchant_id.clone())),
    );

    // Add process to process_tracker for email reminder, only if expiry is set to future date
    // If the `api_key` is set to expire in less than 7 days, the merchant is not notified about it's expiry
    #[cfg(feature = "email")]
    {
        if api_key.expires_at.is_some() {
            let expiry_reminder_days = state.conf.api_keys.get_inner().expiry_reminder_days.clone();

            add_api_key_expiry_task(store, &api_key, expiry_reminder_days)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to insert API key expiry reminder to process tracker")?;
        }
    }

    Ok(ApplicationResponse::Json(
        (api_key, plaintext_api_key).foreign_into(),
    ))
}

// Add api_key_expiry task to the process_tracker table.
// Construct ProcessTrackerNew struct with all required fields, and schedule the first email.
// After first email has been sent, update the schedule_time based on retry_count in execute_workflow().
// A task is not scheduled if the time for the first email is in the past.
#[cfg(feature = "email")]
#[instrument(skip_all)]
pub async fn add_api_key_expiry_task(
    store: &dyn crate::db::StorageInterface,
    api_key: &ApiKey,
    expiry_reminder_days: Vec<u8>,
) -> Result<(), errors::ProcessTrackerError> {
    let current_time = date_time::now();

    let schedule_time = expiry_reminder_days
        .first()
        .and_then(|expiry_reminder_day| {
            api_key.expires_at.map(|expires_at| {
                expires_at.saturating_sub(time::Duration::days(i64::from(*expiry_reminder_day)))
            })
        })
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to obtain initial process tracker schedule time")?;

    if schedule_time <= current_time {
        return Ok(());
    }

    let api_key_expiry_tracker = storage::ApiKeyExpiryTrackingData {
        key_id: api_key.key_id.clone(),
        merchant_id: api_key.merchant_id.clone(),
        api_key_name: api_key.name.clone(),
        prefix: api_key.prefix.clone(),
        // We need API key expiry too, because we need to decide on the schedule_time in
        // execute_workflow() where we won't be having access to the Api key object.
        api_key_expiry: api_key.expires_at,
        expiry_reminder_days: expiry_reminder_days.clone(),
    };

    let process_tracker_id = generate_task_id_for_api_key_expiry_workflow(&api_key.key_id);
    let process_tracker_entry = storage::ProcessTrackerNew::new(
        process_tracker_id,
        API_KEY_EXPIRY_NAME,
        API_KEY_EXPIRY_RUNNER,
        [API_KEY_EXPIRY_TAG],
        api_key_expiry_tracker,
        None,
        schedule_time,
        common_types::consts::API_VERSION,
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to construct API key expiry process tracker task")?;

    store
        .insert_process(process_tracker_entry)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable_lazy(|| {
            format!(
                "Failed while inserting API key expiry reminder to process_tracker: {:?}",
                api_key.key_id
            )
        })?;
    metrics::TASKS_ADDED_COUNT.add(1, router_env::metric_attributes!(("flow", "ApiKeyExpiry")));

    Ok(())
}

#[instrument(skip_all)]
pub async fn retrieve_api_key(
    state: SessionState,
    merchant_id: common_utils::id_type::MerchantId,
    key_id: common_utils::id_type::ApiKeyId,
) -> RouterResponse<api::RetrieveApiKeyResponse> {
    let store = state.store.as_ref();
    let api_key = store
        .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &key_id)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed
        .attach_printable("Failed to retrieve API key")?
        .ok_or(report!(errors::ApiErrorResponse::ApiKeyNotFound))?; // If retrieve returned `None`

    Ok(ApplicationResponse::Json(api_key.foreign_into()))
}

#[instrument(skip_all)]
pub async fn update_api_key(
    state: SessionState,
    api_key: api::UpdateApiKeyRequest,
) -> RouterResponse<api::RetrieveApiKeyResponse> {
    let merchant_id = api_key.merchant_id.clone();
    let key_id = api_key.key_id.clone();
    let store = state.store.as_ref();

    let api_key = store
        .update_api_key(
            merchant_id.to_owned(),
            key_id.to_owned(),
            api_key.foreign_into(),
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::ApiKeyNotFound)?;

    let state_inner = state.clone();
    let hashed_api_key = api_key.hashed_api_key.clone();
    let key_id_inner = api_key.key_id.clone();
    let expires_at = api_key.expires_at;

    authentication::decision::spawn_tracked_job(
        async move {
            authentication::decision::add_api_key(
                &state_inner,
                hashed_api_key.into_inner().into(),
                merchant_id.clone(),
                key_id_inner,
                expires_at.map(authentication::decision::convert_expiry),
            )
            .await
        },
        authentication::decision::ADD,
    );

    #[cfg(feature = "email")]
    {
        let expiry_reminder_days = state.conf.api_keys.get_inner().expiry_reminder_days.clone();

        let task_id = generate_task_id_for_api_key_expiry_workflow(&key_id);
        // In order to determine how to update the existing process in the process_tracker table,
        // we need access to the current entry in the table.
        let existing_process_tracker_task = store
            .find_process_by_id(task_id.as_str())
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed
            .attach_printable(
                "Failed to retrieve API key expiry reminder task from process tracker",
            )?;

        // If process exist
        if existing_process_tracker_task.is_some() {
            if api_key.expires_at.is_some() {
                // Process exist in process, update the process with new schedule_time
                update_api_key_expiry_task(store, &api_key, expiry_reminder_days)
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable(
                        "Failed to update API key expiry reminder task in process tracker",
                    )?;
            }
            // If an expiry is set to 'never'
            else {
                // Process exist in process, revoke it
                revoke_api_key_expiry_task(store, &key_id)
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable(
                        "Failed to revoke API key expiry reminder task in process tracker",
                    )?;
            }
        }
        // This case occurs if the expiry for an API key is set to 'never' during its creation. If so,
        // process in tracker was not created.
        else if api_key.expires_at.is_some() {
            // Process doesn't exist in process_tracker table, so create new entry with
            // schedule_time based on new expiry set.
            add_api_key_expiry_task(store, &api_key, expiry_reminder_days)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(
                    "Failed to insert API key expiry reminder task to process tracker",
                )?;
        }
    }

    Ok(ApplicationResponse::Json(api_key.foreign_into()))
}

// Update api_key_expiry task in the process_tracker table.
// Construct Update variant of ProcessTrackerUpdate with new tracking_data.
// A task is not scheduled if the time for the first email is in the past.
#[cfg(feature = "email")]
#[instrument(skip_all)]
pub async fn update_api_key_expiry_task(
    store: &dyn crate::db::StorageInterface,
    api_key: &ApiKey,
    expiry_reminder_days: Vec<u8>,
) -> Result<(), errors::ProcessTrackerError> {
    let current_time = date_time::now();

    let schedule_time = expiry_reminder_days
        .first()
        .and_then(|expiry_reminder_day| {
            api_key.expires_at.map(|expires_at| {
                expires_at.saturating_sub(time::Duration::days(i64::from(*expiry_reminder_day)))
            })
        });

    if let Some(schedule_time) = schedule_time {
        if schedule_time <= current_time {
            return Ok(());
        }
    }

    let task_id = generate_task_id_for_api_key_expiry_workflow(&api_key.key_id);

    let task_ids = vec![task_id.clone()];

    let updated_tracking_data = &storage::ApiKeyExpiryTrackingData {
        key_id: api_key.key_id.clone(),
        merchant_id: api_key.merchant_id.clone(),
        api_key_name: api_key.name.clone(),
        prefix: api_key.prefix.clone(),
        api_key_expiry: api_key.expires_at,
        expiry_reminder_days,
    };

    let updated_api_key_expiry_workflow_model = serde_json::to_value(updated_tracking_data)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable_lazy(|| {
            format!("unable to serialize API key expiry tracker: {updated_tracking_data:?}")
        })?;

    let updated_process_tracker_data = storage::ProcessTrackerUpdate::Update {
        name: None,
        retry_count: Some(0),
        schedule_time,
        tracking_data: Some(updated_api_key_expiry_workflow_model),
        business_status: Some(String::from(
            diesel_models::process_tracker::business_status::PENDING,
        )),
        status: Some(storage_enums::ProcessTrackerStatus::New),
        updated_at: Some(current_time),
    };
    store
        .process_tracker_update_process_status_by_ids(task_ids, updated_process_tracker_data)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    Ok(())
}

#[instrument(skip_all)]
pub async fn revoke_api_key(
    state: SessionState,
    merchant_id: common_utils::id_type::MerchantId,
    key_id: &common_utils::id_type::ApiKeyId,
) -> RouterResponse<api::RevokeApiKeyResponse> {
    let store = state.store.as_ref();

    let api_key = store
        .find_api_key_by_merchant_id_key_id_optional(&merchant_id, key_id)
        .await
        .to_not_found_response(errors::ApiErrorResponse::ApiKeyNotFound)?;

    let revoked = store
        .revoke_api_key(&merchant_id, key_id)
        .await
        .to_not_found_response(errors::ApiErrorResponse::ApiKeyNotFound)?;

    if let Some(api_key) = api_key {
        let hashed_api_key = api_key.hashed_api_key;
        let state = state.clone();

        authentication::decision::spawn_tracked_job(
            async move {
                authentication::decision::revoke_api_key(&state, hashed_api_key.into_inner().into())
                    .await
            },
            authentication::decision::REVOKE,
        );
    }

    metrics::API_KEY_REVOKED.add(1, &[]);

    #[cfg(feature = "email")]
    {
        let task_id = generate_task_id_for_api_key_expiry_workflow(key_id);
        // In order to determine how to update the existing process in the process_tracker table,
        // we need access to the current entry in the table.
        let existing_process_tracker_task = store
            .find_process_by_id(task_id.as_str())
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed
            .attach_printable(
                "Failed to retrieve API key expiry reminder task from process tracker",
            )?;

        // If process exist, then revoke it
        if existing_process_tracker_task.is_some() {
            revoke_api_key_expiry_task(store, key_id)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(
                    "Failed to revoke API key expiry reminder task in process tracker",
                )?;
        }
    }

    Ok(ApplicationResponse::Json(api::RevokeApiKeyResponse {
        merchant_id: merchant_id.to_owned(),
        key_id: key_id.to_owned(),
        revoked,
    }))
}

// Function to revoke api_key_expiry task in the process_tracker table when API key is revoked.
// Construct StatusUpdate variant of ProcessTrackerUpdate by setting status to 'finish'.
#[cfg(feature = "email")]
#[instrument(skip_all)]
pub async fn revoke_api_key_expiry_task(
    store: &dyn crate::db::StorageInterface,
    key_id: &common_utils::id_type::ApiKeyId,
) -> Result<(), errors::ProcessTrackerError> {
    let task_id = generate_task_id_for_api_key_expiry_workflow(key_id);
    let task_ids = vec![task_id];
    let updated_process_tracker_data = storage::ProcessTrackerUpdate::StatusUpdate {
        status: storage_enums::ProcessTrackerStatus::Finish,
        business_status: Some(String::from(diesel_models::business_status::REVOKED)),
    };

    store
        .process_tracker_update_process_status_by_ids(task_ids, updated_process_tracker_data)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    Ok(())
}

#[instrument(skip_all)]
pub async fn list_api_keys(
    state: SessionState,
    merchant_id: common_utils::id_type::MerchantId,
    limit: Option<i64>,
    offset: Option<i64>,
) -> RouterResponse<Vec<api::RetrieveApiKeyResponse>> {
    let store = state.store.as_ref();
    let api_keys = store
        .list_api_keys_by_merchant_id(&merchant_id, limit, offset)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to list merchant API keys")?;
    let api_keys = api_keys
        .into_iter()
        .map(ForeignInto::foreign_into)
        .collect();

    Ok(ApplicationResponse::Json(api_keys))
}

#[cfg(feature = "email")]
fn generate_task_id_for_api_key_expiry_workflow(
    key_id: &common_utils::id_type::ApiKeyId,
) -> String {
    format!(
        "{API_KEY_EXPIRY_RUNNER}_{API_KEY_EXPIRY_NAME}_{}",
        key_id.get_string_repr()
    )
}

impl From<&str> for PlaintextApiKey {
    fn from(s: &str) -> Self {
        Self(s.to_owned().into())
    }
}

impl From<String> for PlaintextApiKey {
    fn from(s: String) -> Self {
        Self(s.into())
    }
}

impl From<HashedApiKey> for storage::HashedApiKey {
    fn from(hashed_api_key: HashedApiKey) -> Self {
        hashed_api_key.0.into()
    }
}

impl From<storage::HashedApiKey> for HashedApiKey {
    fn from(hashed_api_key: storage::HashedApiKey) -> Self {
        Self(hashed_api_key.into_inner())
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::expect_used, clippy::unwrap_used)]
    use super::*;

    #[tokio::test]
    async fn test_hashing_and_verification() {
        let settings = settings::Settings::new().expect("invalid settings");

        let plaintext_api_key = PlaintextApiKey::new(consts::API_KEY_LENGTH);
        let hash_key = settings.api_keys.get_inner().get_hash_key().unwrap();
        let hashed_api_key = plaintext_api_key.keyed_hash(hash_key.peek());

        assert_ne!(
            plaintext_api_key.0.peek().as_bytes(),
            hashed_api_key.0.as_bytes()
        );

        let new_hashed_api_key = plaintext_api_key.keyed_hash(hash_key.peek());
        assert_eq!(hashed_api_key, new_hashed_api_key)
    }
}


// Struct: BankAccountTokenData
// File: crates/api_models/src/payment_methods.rs
// Module: api_models
// Implementations: 0

pub struct BankAccountTokenData


// File: crates/hyperswitch_constraint_graph/src/dense_map.rs
// Module: hyperswitch_constraint_graph
// Public functions: 12
// Public structs: 5

use std::{fmt, iter, marker::PhantomData, ops, slice, vec};

pub trait EntityId {
    fn get_id(&self) -> usize;
    fn with_id(id: usize) -> Self;
}

macro_rules! impl_entity {
    ($name:ident) => {
        impl $crate::dense_map::EntityId for $name {
            #[inline]
            fn get_id(&self) -> usize {
                self.0
            }

            #[inline]
            fn with_id(id: usize) -> Self {
                Self(id)
            }
        }
    };
}

pub(crate) use impl_entity;

pub struct DenseMap<K, V> {
    data: Vec<V>,
    _marker: PhantomData<K>,
}

impl<K, V> DenseMap<K, V> {
    pub fn new() -> Self {
        Self {
            data: Vec::new(),
            _marker: PhantomData,
        }
    }
}

impl<K, V> Default for DenseMap<K, V> {
    fn default() -> Self {
        Self::new()
    }
}

impl<K, V> DenseMap<K, V>
where
    K: EntityId,
{
    pub fn push(&mut self, elem: V) -> K {
        let curr_len = self.data.len();
        self.data.push(elem);
        K::with_id(curr_len)
    }

    #[inline]
    pub fn get(&self, idx: K) -> Option<&V> {
        self.data.get(idx.get_id())
    }

    #[inline]
    pub fn get_mut(&mut self, idx: K) -> Option<&mut V> {
        self.data.get_mut(idx.get_id())
    }

    #[inline]
    pub fn contains_key(&self, key: K) -> bool {
        key.get_id() < self.data.len()
    }

    #[inline]
    pub fn keys(&self) -> Keys<K> {
        Keys::new(0..self.data.len())
    }

    #[inline]
    pub fn into_keys(self) -> Keys<K> {
        Keys::new(0..self.data.len())
    }

    #[inline]
    pub fn values(&self) -> slice::Iter<'_, V> {
        self.data.iter()
    }

    #[inline]
    pub fn values_mut(&mut self) -> slice::IterMut<'_, V> {
        self.data.iter_mut()
    }

    #[inline]
    pub fn into_values(self) -> vec::IntoIter<V> {
        self.data.into_iter()
    }

    #[inline]
    pub fn iter(&self) -> Iter<'_, K, V> {
        Iter::new(self.data.iter())
    }

    #[inline]
    pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
        IterMut::new(self.data.iter_mut())
    }
}

impl<K, V> fmt::Debug for DenseMap<K, V>
where
    K: EntityId + fmt::Debug,
    V: fmt::Debug,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_map().entries(self.iter()).finish()
    }
}

pub struct Keys<K> {
    inner: ops::Range<usize>,
    _marker: PhantomData<K>,
}

impl<K> Keys<K> {
    fn new(range: ops::Range<usize>) -> Self {
        Self {
            inner: range,
            _marker: PhantomData,
        }
    }
}

impl<K> Iterator for Keys<K>
where
    K: EntityId,
{
    type Item = K;

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().map(K::with_id)
    }
}

pub struct Iter<'a, K, V> {
    inner: iter::Enumerate<slice::Iter<'a, V>>,
    _marker: PhantomData<K>,
}

impl<'a, K, V> Iter<'a, K, V> {
    fn new(iter: slice::Iter<'a, V>) -> Self {
        Self {
            inner: iter.enumerate(),
            _marker: PhantomData,
        }
    }
}

impl<'a, K, V> Iterator for Iter<'a, K, V>
where
    K: EntityId,
{
    type Item = (K, &'a V);

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().map(|(id, val)| (K::with_id(id), val))
    }
}

pub struct IterMut<'a, K, V> {
    inner: iter::Enumerate<slice::IterMut<'a, V>>,
    _marker: PhantomData<K>,
}

impl<'a, K, V> IterMut<'a, K, V> {
    fn new(iter: slice::IterMut<'a, V>) -> Self {
        Self {
            inner: iter.enumerate(),
            _marker: PhantomData,
        }
    }
}

impl<'a, K, V> Iterator for IterMut<'a, K, V>
where
    K: EntityId,
{
    type Item = (K, &'a mut V);

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().map(|(id, val)| (K::with_id(id), val))
    }
}

pub struct IntoIter<K, V> {
    inner: iter::Enumerate<vec::IntoIter<V>>,
    _marker: PhantomData<K>,
}

impl<K, V> IntoIter<K, V> {
    fn new(iter: vec::IntoIter<V>) -> Self {
        Self {
            inner: iter.enumerate(),
            _marker: PhantomData,
        }
    }
}

impl<K, V> Iterator for IntoIter<K, V>
where
    K: EntityId,
{
    type Item = (K, V);

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().map(|(id, val)| (K::with_id(id), val))
    }
}

impl<K, V> IntoIterator for DenseMap<K, V>
where
    K: EntityId,
{
    type Item = (K, V);
    type IntoIter = IntoIter<K, V>;

    fn into_iter(self) -> Self::IntoIter {
        IntoIter::new(self.data.into_iter())
    }
}

impl<K, V> FromIterator<V> for DenseMap<K, V>
where
    K: EntityId,
{
    fn from_iter<T>(iter: T) -> Self
    where
        T: IntoIterator<Item = V>,
    {
        Self {
            data: Vec::from_iter(iter),
            _marker: PhantomData,
        }
    }
}