// File: crates/router/src/core/disputes.rs
// Module: router
// Public functions: 17

use std::{collections::HashMap, ops::Deref, str::FromStr};

use api_models::{
    admin::MerchantConnectorInfo, disputes as dispute_models, files as files_api_models,
};
use common_utils::ext_traits::{Encode, ValueExt};
use error_stack::ResultExt;
use router_env::{
    instrument, logger,
    tracing::{self, Instrument},
};
use strum::IntoEnumIterator;
pub mod transformers;

use super::{
    errors::{self, ConnectorErrorExt, RouterResponse, StorageErrorExt},
    metrics,
};
use crate::{
    core::{files, payments, utils as core_utils, webhooks},
    routes::{app::StorageInterface, metrics::TASKS_ADDED_COUNT, SessionState},
    services,
    types::{
        api::{self, disputes},
        domain,
        storage::enums as storage_enums,
        transformers::{ForeignFrom, ForeignInto},
        AcceptDisputeRequestData, AcceptDisputeResponse, DefendDisputeRequestData,
        DefendDisputeResponse, DisputePayload, DisputeSyncData, DisputeSyncResponse,
        FetchDisputesRequestData, FetchDisputesResponse, SubmitEvidenceRequestData,
        SubmitEvidenceResponse,
    },
    workflows::process_dispute,
};

pub(crate) fn should_call_connector_for_dispute_sync(
    force_sync: Option<bool>,
    dispute_status: storage_enums::DisputeStatus,
) -> bool {
    force_sync == Some(true)
        && matches!(
            dispute_status,
            common_enums::DisputeStatus::DisputeAccepted
                | common_enums::DisputeStatus::DisputeChallenged
                | common_enums::DisputeStatus::DisputeOpened
        )
}

#[instrument(skip(state))]
pub async fn retrieve_dispute(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    req: dispute_models::DisputeRetrieveRequest,
) -> RouterResponse<api_models::disputes::DisputeResponse> {
    let dispute = state
        .store
        .find_dispute_by_merchant_id_dispute_id(
            merchant_context.get_merchant_account().get_id(),
            &req.dispute_id,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: req.dispute_id,
        })?;
    core_utils::validate_profile_id_from_auth_layer(profile_id.clone(), &dispute)?;

    #[cfg(feature = "v1")]
    let dispute_response =
        if should_call_connector_for_dispute_sync(req.force_sync, dispute.dispute_status) {
            let db = &state.store;
            core_utils::validate_profile_id_from_auth_layer(profile_id.clone(), &dispute)?;
            let payment_intent = db
                .find_payment_intent_by_payment_id_merchant_id(
                    &(&state).into(),
                    &dispute.payment_id,
                    merchant_context.get_merchant_account().get_id(),
                    merchant_context.get_merchant_key_store(),
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::PaymentNotFound)?;

            let payment_attempt = db
                .find_payment_attempt_by_attempt_id_merchant_id(
                    &dispute.attempt_id,
                    merchant_context.get_merchant_account().get_id(),
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::PaymentNotFound)?;

            let connector_data = api::ConnectorData::get_connector_by_name(
                &state.conf.connectors,
                &dispute.connector,
                api::GetToken::Connector,
                dispute.merchant_connector_id.clone(),
            )?;

            let connector_integration: services::BoxedDisputeConnectorIntegrationInterface<
                api::Dsync,
                DisputeSyncData,
                DisputeSyncResponse,
            > = connector_data.connector.get_connector_integration();
            let router_data = core_utils::construct_dispute_sync_router_data(
                &state,
                &payment_intent,
                &payment_attempt,
                &merchant_context,
                &dispute,
            )
            .await?;
            let response = services::execute_connector_processing_step(
                &state,
                connector_integration,
                &router_data,
                payments::CallConnectorAction::Trigger,
                None,
                None,
            )
            .await
            .to_dispute_failed_response()
            .attach_printable("Failed while calling accept dispute connector api")?;

            let dispute_sync_response = response.response.map_err(|err| {
                errors::ApiErrorResponse::ExternalConnectorError {
                    code: err.code,
                    message: err.message,
                    connector: dispute.connector.clone(),
                    status_code: err.status_code,
                    reason: err.reason,
                }
            })?;

            let business_profile = state
                .store
                .find_business_profile_by_profile_id(
                    &(&state).into(),
                    merchant_context.get_merchant_key_store(),
                    &payment_attempt.profile_id,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
                    id: payment_attempt.profile_id.get_string_repr().to_owned(),
                })?;

            update_dispute_data(
                &state,
                merchant_context,
                business_profile,
                Some(dispute.clone()),
                dispute_sync_response,
                payment_attempt,
                dispute.connector.as_str(),
            )
            .await
            .attach_printable("Dispute update failed")?
        } else {
            api_models::disputes::DisputeResponse::foreign_from(dispute)
        };

    #[cfg(not(feature = "v1"))]
    let dispute_response = api_models::disputes::DisputeResponse::foreign_from(dispute);

    Ok(services::ApplicationResponse::Json(dispute_response))
}

#[instrument(skip(state))]
pub async fn retrieve_disputes_list(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
    constraints: api_models::disputes::DisputeListGetConstraints,
) -> RouterResponse<Vec<api_models::disputes::DisputeResponse>> {
    let dispute_list_constraints = &(constraints.clone(), profile_id_list.clone()).try_into()?;
    let disputes = state
        .store
        .find_disputes_by_constraints(
            merchant_context.get_merchant_account().get_id(),
            dispute_list_constraints,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to retrieve disputes")?;
    let disputes_list = disputes
        .into_iter()
        .map(api_models::disputes::DisputeResponse::foreign_from)
        .collect();
    Ok(services::ApplicationResponse::Json(disputes_list))
}

#[cfg(feature = "v2")]
#[instrument(skip(state))]
pub async fn accept_dispute(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    req: disputes::DisputeId,
) -> RouterResponse<dispute_models::DisputeResponse> {
    todo!()
}

#[cfg(feature = "v1")]
#[instrument(skip(state))]
pub async fn get_filters_for_disputes(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
) -> RouterResponse<api_models::disputes::DisputeListFilters> {
    let merchant_connector_accounts = if let services::ApplicationResponse::Json(data) =
        super::admin::list_payment_connectors(
            state,
            merchant_context.get_merchant_account().get_id().to_owned(),
            profile_id_list,
        )
        .await?
    {
        data
    } else {
        return Err(error_stack::report!(
            errors::ApiErrorResponse::InternalServerError
        ))
        .attach_printable(
            "Failed to retrieve merchant connector accounts while fetching dispute list filters.",
        );
    };

    let connector_map = merchant_connector_accounts
        .into_iter()
        .filter_map(|merchant_connector_account| {
            merchant_connector_account
                .connector_label
                .clone()
                .map(|label| {
                    let info = merchant_connector_account.to_merchant_connector_info(&label);
                    (merchant_connector_account.connector_name, info)
                })
        })
        .fold(
            HashMap::new(),
            |mut map: HashMap<String, Vec<MerchantConnectorInfo>>, (connector_name, info)| {
                map.entry(connector_name).or_default().push(info);
                map
            },
        );

    Ok(services::ApplicationResponse::Json(
        api_models::disputes::DisputeListFilters {
            connector: connector_map,
            currency: storage_enums::Currency::iter().collect(),
            dispute_status: storage_enums::DisputeStatus::iter().collect(),
            dispute_stage: storage_enums::DisputeStage::iter().collect(),
        },
    ))
}

#[cfg(feature = "v1")]
#[instrument(skip(state))]
pub async fn accept_dispute(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    req: disputes::DisputeId,
) -> RouterResponse<dispute_models::DisputeResponse> {
    let db = &state.store;
    let dispute = state
        .store
        .find_dispute_by_merchant_id_dispute_id(
            merchant_context.get_merchant_account().get_id(),
            &req.dispute_id,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: req.dispute_id,
        })?;
    core_utils::validate_profile_id_from_auth_layer(profile_id, &dispute)?;
    let dispute_id = dispute.dispute_id.clone();
    common_utils::fp_utils::when(
        !core_utils::should_proceed_with_accept_dispute(
            dispute.dispute_stage,
            dispute.dispute_status,
        ),
        || {
            metrics::ACCEPT_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add(1, &[]);
            Err(errors::ApiErrorResponse::DisputeStatusValidationFailed {
            reason: format!(
                "This dispute cannot be accepted because the dispute is in {} stage and has {} status",
                dispute.dispute_stage, dispute.dispute_status
            ),
        })
        },
    )?;

    let payment_intent = db
        .find_payment_intent_by_payment_id_merchant_id(
            &(&state).into(),
            &dispute.payment_id,
            merchant_context.get_merchant_account().get_id(),
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::PaymentNotFound)?;

    let payment_attempt = db
        .find_payment_attempt_by_attempt_id_merchant_id(
            &dispute.attempt_id,
            merchant_context.get_merchant_account().get_id(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::PaymentNotFound)?;
    let connector_data = api::ConnectorData::get_connector_by_name(
        &state.conf.connectors,
        &dispute.connector,
        api::GetToken::Connector,
        dispute.merchant_connector_id.clone(),
    )?;
    let connector_integration: services::BoxedDisputeConnectorIntegrationInterface<
        api::Accept,
        AcceptDisputeRequestData,
        AcceptDisputeResponse,
    > = connector_data.connector.get_connector_integration();
    let router_data = core_utils::construct_accept_dispute_router_data(
        &state,
        &payment_intent,
        &payment_attempt,
        &merchant_context,
        &dispute,
    )
    .await?;
    let response = services::execute_connector_processing_step(
        &state,
        connector_integration,
        &router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_dispute_failed_response()
    .attach_printable("Failed while calling accept dispute connector api")?;
    let accept_dispute_response =
        response
            .response
            .map_err(|err| errors::ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: dispute.connector.clone(),
                status_code: err.status_code,
                reason: err.reason,
            })?;
    let update_dispute = diesel_models::dispute::DisputeUpdate::StatusUpdate {
        dispute_status: accept_dispute_response.dispute_status,
        connector_status: accept_dispute_response.connector_status.clone(),
    };
    let updated_dispute = db
        .update_dispute(dispute.clone(), update_dispute)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable_lazy(|| {
            format!("Unable to update dispute with dispute_id: {dispute_id}")
        })?;
    let dispute_response = api_models::disputes::DisputeResponse::foreign_from(updated_dispute);
    Ok(services::ApplicationResponse::Json(dispute_response))
}

#[cfg(feature = "v2")]
#[instrument(skip(state))]
pub async fn submit_evidence(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    req: dispute_models::SubmitEvidenceRequest,
) -> RouterResponse<dispute_models::DisputeResponse> {
    todo!()
}

#[cfg(feature = "v1")]
#[instrument(skip(state))]
pub async fn submit_evidence(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    req: dispute_models::SubmitEvidenceRequest,
) -> RouterResponse<dispute_models::DisputeResponse> {
    let db = &state.store;
    let dispute = state
        .store
        .find_dispute_by_merchant_id_dispute_id(
            merchant_context.get_merchant_account().get_id(),
            &req.dispute_id,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: req.dispute_id.clone(),
        })?;
    core_utils::validate_profile_id_from_auth_layer(profile_id, &dispute)?;
    let dispute_id = dispute.dispute_id.clone();
    common_utils::fp_utils::when(
        !core_utils::should_proceed_with_submit_evidence(
            dispute.dispute_stage,
            dispute.dispute_status,
        ),
        || {
            metrics::EVIDENCE_SUBMISSION_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add(1, &[]);
            Err(errors::ApiErrorResponse::DisputeStatusValidationFailed {
                reason: format!(
                "Evidence cannot be submitted because the dispute is in {} stage and has {} status",
                dispute.dispute_stage, dispute.dispute_status
            ),
            })
        },
    )?;
    let submit_evidence_request_data =
        transformers::get_evidence_request_data(&state, &merchant_context, req, &dispute).await?;

    let payment_intent = db
        .find_payment_intent_by_payment_id_merchant_id(
            &(&state).into(),
            &dispute.payment_id,
            merchant_context.get_merchant_account().get_id(),
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::PaymentNotFound)?;

    let payment_attempt = db
        .find_payment_attempt_by_attempt_id_merchant_id(
            &dispute.attempt_id,
            merchant_context.get_merchant_account().get_id(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::PaymentNotFound)?;
    let connector_data = api::ConnectorData::get_connector_by_name(
        &state.conf.connectors,
        &dispute.connector,
        api::GetToken::Connector,
        dispute.merchant_connector_id.clone(),
    )?;

    let connector_integration: services::BoxedDisputeConnectorIntegrationInterface<
        api::Evidence,
        SubmitEvidenceRequestData,
        SubmitEvidenceResponse,
    > = connector_data.connector.get_connector_integration();
    let router_data = core_utils::construct_submit_evidence_router_data(
        &state,
        &payment_intent,
        &payment_attempt,
        &merchant_context,
        &dispute,
        submit_evidence_request_data,
    )
    .await?;
    let response = services::execute_connector_processing_step(
        &state,
        connector_integration,
        &router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_dispute_failed_response()
    .attach_printable("Failed while calling submit evidence connector api")?;
    let submit_evidence_response =
        response
            .response
            .map_err(|err| errors::ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: dispute.connector.clone(),
                status_code: err.status_code,
                reason: err.reason,
            })?;
    //Defend Dispute Optionally if connector expects to defend / submit evidence in a separate api call
    let (dispute_status, connector_status) = if connector_data
        .connector_name
        .requires_defend_dispute()
    {
        let connector_integration_defend_dispute: services::BoxedDisputeConnectorIntegrationInterface<
                api::Defend,
                DefendDisputeRequestData,
                DefendDisputeResponse,
            > = connector_data.connector.get_connector_integration();
        let defend_dispute_router_data = core_utils::construct_defend_dispute_router_data(
            &state,
            &payment_intent,
            &payment_attempt,
            &merchant_context,
            &dispute,
        )
        .await?;
        let defend_response = services::execute_connector_processing_step(
            &state,
            connector_integration_defend_dispute,
            &defend_dispute_router_data,
            payments::CallConnectorAction::Trigger,
            None,
            None,
        )
        .await
        .to_dispute_failed_response()
        .attach_printable("Failed while calling defend dispute connector api")?;
        let defend_dispute_response = defend_response.response.map_err(|err| {
            errors::ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: dispute.connector.clone(),
                status_code: err.status_code,
                reason: err.reason,
            }
        })?;
        (
            defend_dispute_response.dispute_status,
            defend_dispute_response.connector_status,
        )
    } else {
        (
            submit_evidence_response.dispute_status,
            submit_evidence_response.connector_status,
        )
    };
    let update_dispute = diesel_models::dispute::DisputeUpdate::StatusUpdate {
        dispute_status,
        connector_status,
    };
    let updated_dispute = db
        .update_dispute(dispute.clone(), update_dispute)
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: dispute_id.to_owned(),
        })
        .attach_printable_lazy(|| {
            format!("Unable to update dispute with dispute_id: {dispute_id}")
        })?;
    let dispute_response = api_models::disputes::DisputeResponse::foreign_from(updated_dispute);
    Ok(services::ApplicationResponse::Json(dispute_response))
}

pub async fn attach_evidence(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    attach_evidence_request: api::AttachEvidenceRequest,
) -> RouterResponse<files_api_models::CreateFileResponse> {
    let db = &state.store;
    let dispute_id = attach_evidence_request
        .create_file_request
        .dispute_id
        .clone()
        .ok_or(errors::ApiErrorResponse::MissingDisputeId)?;
    let dispute = db
        .find_dispute_by_merchant_id_dispute_id(
            merchant_context.get_merchant_account().get_id(),
            &dispute_id,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: dispute_id.clone(),
        })?;
    core_utils::validate_profile_id_from_auth_layer(profile_id, &dispute)?;
    common_utils::fp_utils::when(
        !(dispute.dispute_stage == storage_enums::DisputeStage::Dispute
            && dispute.dispute_status == storage_enums::DisputeStatus::DisputeOpened),
        || {
            metrics::ATTACH_EVIDENCE_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add(1, &[]);
            Err(errors::ApiErrorResponse::DisputeStatusValidationFailed {
                reason: format!(
                "Evidence cannot be attached because the dispute is in {} stage and has {} status",
                dispute.dispute_stage, dispute.dispute_status
            ),
            })
        },
    )?;
    let create_file_response = Box::pin(files::files_create_core(
        state.clone(),
        merchant_context,
        attach_evidence_request.create_file_request,
    ))
    .await?;
    let file_id = match &create_file_response {
        services::ApplicationResponse::Json(res) => res.file_id.clone(),
        _ => Err(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Unexpected response received from files create core")?,
    };
    let dispute_evidence: api::DisputeEvidence = dispute
        .evidence
        .clone()
        .parse_value("DisputeEvidence")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error while parsing dispute evidence record")?;
    let updated_dispute_evidence = transformers::update_dispute_evidence(
        dispute_evidence,
        attach_evidence_request.evidence_type,
        file_id,
    );
    let update_dispute = diesel_models::dispute::DisputeUpdate::EvidenceUpdate {
        evidence: updated_dispute_evidence
            .encode_to_value()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error while encoding dispute evidence")?
            .into(),
    };
    db.update_dispute(dispute, update_dispute)
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: dispute_id.to_owned(),
        })
        .attach_printable_lazy(|| {
            format!("Unable to update dispute with dispute_id: {dispute_id}")
        })?;
    Ok(create_file_response)
}

#[instrument(skip(state))]
pub async fn retrieve_dispute_evidence(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    req: disputes::DisputeId,
) -> RouterResponse<Vec<api_models::disputes::DisputeEvidenceBlock>> {
    let dispute = state
        .store
        .find_dispute_by_merchant_id_dispute_id(
            merchant_context.get_merchant_account().get_id(),
            &req.dispute_id,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: req.dispute_id,
        })?;
    core_utils::validate_profile_id_from_auth_layer(profile_id, &dispute)?;
    let dispute_evidence: api::DisputeEvidence = dispute
        .evidence
        .clone()
        .parse_value("DisputeEvidence")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error while parsing dispute evidence record")?;
    let dispute_evidence_vec =
        transformers::get_dispute_evidence_vec(&state, merchant_context, dispute_evidence).await?;
    Ok(services::ApplicationResponse::Json(dispute_evidence_vec))
}

pub async fn delete_evidence(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    delete_evidence_request: dispute_models::DeleteEvidenceRequest,
) -> RouterResponse<serde_json::Value> {
    let dispute_id = delete_evidence_request.dispute_id.clone();
    let dispute = state
        .store
        .find_dispute_by_merchant_id_dispute_id(
            merchant_context.get_merchant_account().get_id(),
            &dispute_id,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: dispute_id.clone(),
        })?;
    let dispute_evidence: api::DisputeEvidence = dispute
        .evidence
        .clone()
        .parse_value("DisputeEvidence")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error while parsing dispute evidence record")?;
    let updated_dispute_evidence =
        transformers::delete_evidence_file(dispute_evidence, delete_evidence_request.evidence_type);
    let update_dispute = diesel_models::dispute::DisputeUpdate::EvidenceUpdate {
        evidence: updated_dispute_evidence
            .encode_to_value()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error while encoding dispute evidence")?
            .into(),
    };
    state
        .store
        .update_dispute(dispute, update_dispute)
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: dispute_id.to_owned(),
        })
        .attach_printable_lazy(|| {
            format!("Unable to update dispute with dispute_id: {dispute_id}")
        })?;
    Ok(services::ApplicationResponse::StatusOk)
}

#[instrument(skip(state))]
pub async fn get_aggregates_for_disputes(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
    time_range: common_utils::types::TimeRange,
) -> RouterResponse<dispute_models::DisputesAggregateResponse> {
    let db = state.store.as_ref();
    let dispute_status_with_count = db
        .get_dispute_status_with_count(
            merchant_context.get_merchant_account().get_id(),
            profile_id_list,
            &time_range,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to retrieve disputes aggregate")?;

    let mut status_map: HashMap<storage_enums::DisputeStatus, i64> =
        dispute_status_with_count.into_iter().collect();

    for status in storage_enums::DisputeStatus::iter() {
        status_map.entry(status).or_default();
    }

    Ok(services::ApplicationResponse::Json(
        dispute_models::DisputesAggregateResponse {
            status_with_count: status_map,
        },
    ))
}

#[cfg(feature = "v1")]
#[instrument(skip(state))]
pub async fn connector_sync_disputes(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    merchant_connector_id: String,
    payload: disputes::DisputeFetchQueryData,
) -> RouterResponse<FetchDisputesResponse> {
    let connector_id =
        common_utils::id_type::MerchantConnectorAccountId::wrap(merchant_connector_id)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse merchant connector account id format")?;
    let format = time::format_description::parse("[year]-[month]-[day]T[hour]:[minute]:[second]")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to parse the date-time format")?;
    let created_from = time::PrimitiveDateTime::parse(&payload.fetch_from, &format)
        .change_context(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "fetch_from".to_string(),
            expected_format: "YYYY-MM-DDTHH:MM:SS".to_string(),
        })?;
    let created_till = time::PrimitiveDateTime::parse(&payload.fetch_till, &format)
        .change_context(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "fetch_till".to_string(),
            expected_format: "YYYY-MM-DDTHH:MM:SS".to_string(),
        })?;
    let fetch_dispute_request = FetchDisputesRequestData {
        created_from,
        created_till,
    };
    Box::pin(fetch_disputes_from_connector(
        state,
        merchant_context,
        connector_id,
        fetch_dispute_request,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip(state))]
pub async fn fetch_disputes_from_connector(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId,
    req: FetchDisputesRequestData,
) -> RouterResponse<FetchDisputesResponse> {
    let db = &*state.store;
    let key_manager_state = &(&state).into();
    let merchant_id = merchant_context.get_merchant_account().get_id();
    let merchant_connector_account = db
        .find_by_merchant_connector_account_merchant_id_merchant_connector_id(
            key_manager_state,
            merchant_id,
            &merchant_connector_id,
            merchant_context.get_merchant_key_store(),
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
            id: merchant_connector_id.get_string_repr().to_string(),
        })?;
    let connector_name = merchant_connector_account.connector_name.clone();
    let connector_data = api::ConnectorData::get_connector_by_name(
        &state.conf.connectors,
        &connector_name,
        api::GetToken::Connector,
        Some(merchant_connector_id.clone()),
    )?;
    let connector_integration: services::BoxedDisputeConnectorIntegrationInterface<
        api::Fetch,
        FetchDisputesRequestData,
        FetchDisputesResponse,
    > = connector_data.connector.get_connector_integration();

    let router_data =
        core_utils::construct_dispute_list_router_data(&state, merchant_connector_account, req)
            .await?;

    let response = services::execute_connector_processing_step(
        &state,
        connector_integration,
        &router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_dispute_failed_response()
    .attach_printable("Failed while calling accept dispute connector api")?;
    let fetch_dispute_response =
        response
            .response
            .map_err(|err| errors::ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: connector_name.clone(),
                status_code: err.status_code,
                reason: err.reason,
            })?;

    for dispute in &fetch_dispute_response {
        // check if payment already exist
        let payment_attempt = webhooks::incoming::get_payment_attempt_from_object_reference_id(
            &state,
            dispute.object_reference_id.clone(),
            &merchant_context,
        )
        .await;

        if payment_attempt.is_ok() {
            let schedule_time = process_dispute::get_sync_process_schedule_time(
                &*state.store,
                &connector_name,
                merchant_id,
                0,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed while getting process schedule time")?;

            let response = add_process_dispute_task_to_pt(
                db,
                &connector_name,
                dispute,
                merchant_id.clone(),
                schedule_time,
            )
            .await;

            match response {
                Err(report)
                    if report
                        .downcast_ref::<errors::StorageError>()
                        .is_some_and(|error| {
                            matches!(error, errors::StorageError::DuplicateValue { .. })
                        }) =>
                {
                    Ok(())
                }
                Ok(_) => Ok(()),
                Err(_) => Err(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed while adding task to process tracker"),
            }?;
        } else {
            router_env::logger::info!("Disputed payment does not exist in our records");
        }
    }

    Ok(services::ApplicationResponse::Json(fetch_dispute_response))
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn update_dispute_data(
    state: &SessionState,
    merchant_context: domain::MerchantContext,
    business_profile: domain::Profile,
    option_dispute: Option<diesel_models::dispute::Dispute>,
    dispute_details: DisputeSyncResponse,
    payment_attempt: domain::PaymentAttempt,
    connector_name: &str,
) -> errors::CustomResult<api_models::disputes::DisputeResponse, errors::ApiErrorResponse> {
    let dispute_data = DisputePayload::from(dispute_details.clone());
    let dispute_object = webhooks::incoming::get_or_update_dispute_object(
        state.clone(),
        option_dispute,
        dispute_data,
        merchant_context.get_merchant_account().get_id(),
        &merchant_context.get_merchant_account().organization_id,
        &payment_attempt,
        dispute_details.dispute_status,
        &business_profile,
        connector_name,
    )
    .await?;
    let disputes_response: dispute_models::DisputeResponse = dispute_object.clone().foreign_into();
    let event_type: storage_enums::EventType = dispute_details.dispute_status.into();

    Box::pin(webhooks::create_event_and_trigger_outgoing_webhook(
        state.clone(),
        merchant_context,
        business_profile,
        event_type,
        storage_enums::EventClass::Disputes,
        dispute_object.dispute_id.clone(),
        storage_enums::EventObjectType::DisputeDetails,
        api::OutgoingWebhookContent::DisputeDetails(Box::new(disputes_response.clone())),
        Some(dispute_object.created_at),
    ))
    .await?;
    Ok(disputes_response)
}

#[cfg(feature = "v1")]
pub async fn add_process_dispute_task_to_pt(
    db: &dyn StorageInterface,
    connector_name: &str,
    dispute_payload: &DisputeSyncResponse,
    merchant_id: common_utils::id_type::MerchantId,
    schedule_time: Option<time::PrimitiveDateTime>,
) -> common_utils::errors::CustomResult<(), errors::StorageError> {
    match schedule_time {
        Some(time) => {
            TASKS_ADDED_COUNT.add(
                1,
                router_env::metric_attributes!(("flow", "dispute_process")),
            );
            let tracking_data = disputes::ProcessDisputePTData {
                connector_name: connector_name.to_string(),
                dispute_payload: dispute_payload.clone(),
                merchant_id: merchant_id.clone(),
            };
            let runner = common_enums::ProcessTrackerRunner::ProcessDisputeWorkflow;
            let task = "DISPUTE_PROCESS";
            let tag = ["PROCESS", "DISPUTE"];
            let process_tracker_id = scheduler::utils::get_process_tracker_id(
                runner,
                task,
                &dispute_payload.connector_dispute_id.clone(),
                &merchant_id,
            );
            let process_tracker_entry = diesel_models::ProcessTrackerNew::new(
                process_tracker_id,
                task,
                runner,
                tag,
                tracking_data,
                None,
                time,
                common_types::consts::API_VERSION,
            )
            .map_err(errors::StorageError::from)?;
            db.insert_process(process_tracker_entry).await?;
            Ok(())
        }
        None => Ok(()),
    }
}

#[cfg(feature = "v1")]
pub async fn add_dispute_list_task_to_pt(
    db: &dyn StorageInterface,
    connector_name: &str,
    merchant_id: common_utils::id_type::MerchantId,
    merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId,
    profile_id: common_utils::id_type::ProfileId,
    fetch_request: FetchDisputesRequestData,
) -> common_utils::errors::CustomResult<(), errors::StorageError> {
    TASKS_ADDED_COUNT.add(1, router_env::metric_attributes!(("flow", "dispute_list")));
    let tracking_data = disputes::DisputeListPTData {
        connector_name: connector_name.to_string(),
        merchant_id: merchant_id.clone(),
        merchant_connector_id: merchant_connector_id.clone(),
        created_from: fetch_request.created_from,
        created_till: fetch_request.created_till,
        profile_id,
    };
    let runner = common_enums::ProcessTrackerRunner::DisputeListWorkflow;
    let task = "DISPUTE_LIST";
    let tag = ["LIST", "DISPUTE"];
    let process_tracker_id = scheduler::utils::get_process_tracker_id_for_dispute_list(
        runner,
        &merchant_connector_id,
        fetch_request.created_from,
        &merchant_id,
    );
    let process_tracker_entry = diesel_models::ProcessTrackerNew::new(
        process_tracker_id,
        task,
        runner,
        tag,
        tracking_data,
        None,
        fetch_request.created_from,
        common_types::consts::API_VERSION,
    )
    .map_err(errors::StorageError::from)?;
    db.insert_process(process_tracker_entry).await?;
    Ok(())
}

#[cfg(feature = "v1")]
pub async fn schedule_dispute_sync_task(
    state: &SessionState,
    business_profile: &domain::Profile,
    mca: &domain::MerchantConnectorAccount,
) -> common_utils::errors::CustomResult<(), errors::ApiErrorResponse> {
    let connector = api::enums::Connector::from_str(&mca.connector_name).change_context(
        errors::ApiErrorResponse::InvalidDataValue {
            field_name: "connector",
        },
    )?;

    if core_utils::should_add_dispute_sync_task_to_pt(state, connector) {
        let offset_date_time = time::OffsetDateTime::now_utc();
        let created_from =
            time::PrimitiveDateTime::new(offset_date_time.date(), offset_date_time.time());
        let dispute_polling_interval = *business_profile
            .dispute_polling_interval
            .unwrap_or_default()
            .deref();

        let created_till = created_from
            .checked_add(time::Duration::hours(i64::from(dispute_polling_interval)))
            .ok_or(errors::ApiErrorResponse::InternalServerError)?;

        let m_db = state.clone().store;
        let connector_name = mca.connector_name.clone();
        let merchant_id = mca.merchant_id.clone();
        let merchant_connector_id = mca.merchant_connector_id.clone();
        let business_profile_id = business_profile.get_id().clone();

        tokio::spawn(
            async move {
                add_dispute_list_task_to_pt(
                    &*m_db,
                    &connector_name,
                    merchant_id.clone(),
                    merchant_connector_id.clone(),
                    business_profile_id,
                    FetchDisputesRequestData {
                        created_from,
                        created_till,
                    },
                )
                .await
                .map_err(|error| {
                    logger::error!("Failed to add dispute list task to process tracker: {error}")
                })
            }
            .in_current_span(),
        );
    }
    Ok(())
}


// File: crates/api_models/src/recon.rs
// Module: api_models
// Public structs: 4

use common_utils::{id_type, pii};
use masking::Secret;

use crate::enums;

#[derive(serde::Deserialize, Debug, serde::Serialize)]
pub struct ReconUpdateMerchantRequest {
    pub recon_status: enums::ReconStatus,
    pub user_email: pii::Email,
}

#[derive(Debug, serde::Serialize)]
pub struct ReconTokenResponse {
    pub token: Secret<String>,
}

#[derive(Debug, serde::Serialize)]
pub struct ReconStatusResponse {
    pub recon_status: enums::ReconStatus,
}

#[derive(serde::Serialize, Debug)]
pub struct VerifyTokenResponse {
    pub merchant_id: id_type::MerchantId,
    pub user_email: pii::Email,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub acl: Option<String>,
}


// Implementation: impl PaymentAttempt
// File: crates/hyperswitch_domain_models/src/payments/payment_attempt.rs
// Module: hyperswitch_domain_models
// Methods: 13 total (9 public)

impl PaymentAttempt


// Implementation: impl ConnectorSpecifications for for Elavon
// File: crates/hyperswitch_connectors/src/connectors/elavon.rs
// Module: hyperswitch_connectors
// Methods: 3 total (0 public)

impl ConnectorSpecifications for for Elavon


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

pub struct CoBadgedCardDataToBeSaved


// Trait: VerifyConnector
// File: crates/router/src/types/api/verify_connector.rs
// Module: router

pub trait VerifyConnector


// Function: make_payout_method_data
// File: crates/router/src/core/payouts/helpers.rs
// Module: router

pub fn make_payout_method_data(
    state: &SessionState,
    payout_method_data: Option<&api::PayoutMethodData>,
    payout_token: Option<&str>,
    customer_id: &id_type::CustomerId,
    merchant_id: &id_type::MerchantId,
    payout_type: Option<api_enums::PayoutType>,
    merchant_key_store: &domain::MerchantKeyStore,
    payout_data: Option<&mut PayoutData>,
    storage_scheme: storage::enums::MerchantStorageScheme,
) -> RouterResult<Option<api::PayoutMethodData>>


// File: crates/masking/src/abs.rs
// Module: masking

//! Abstract data types.

use crate::Secret;

/// Interface to expose a reference to an inner secret
pub trait PeekInterface<S> {
    /// Only method providing access to the secret value.
    fn peek(&self) -> &S;

    /// Provide a mutable reference to the inner value.
    fn peek_mut(&mut self) -> &mut S;
}

/// Interface that consumes a option secret and returns the value.
pub trait ExposeOptionInterface<S> {
    /// Expose option.
    fn expose_option(self) -> S;
}

/// Interface that consumes a secret and returns the inner value.
pub trait ExposeInterface<S> {
    /// Consume the secret and return the inner value
    fn expose(self) -> S;
}

impl<S, I> ExposeOptionInterface<Option<S>> for Option<Secret<S, I>>
where
    S: Clone,
    I: crate::Strategy<S>,
{
    fn expose_option(self) -> Option<S> {
        self.map(ExposeInterface::expose)
    }
}

impl<S, I> ExposeInterface<S> for Secret<S, I>
where
    I: crate::Strategy<S>,
{
    fn expose(self) -> S {
        self.inner_secret
    }
}

/// Interface that consumes a secret and converts it to a secret with a different masking strategy.
pub trait SwitchStrategy<FromStrategy, ToStrategy> {
    /// The type returned by `switch_strategy()`.
    type Output;

    /// Consumes the secret and converts it to a secret with a different masking strategy.
    fn switch_strategy(self) -> Self::Output;
}

impl<S, FromStrategy, ToStrategy> SwitchStrategy<FromStrategy, ToStrategy>
    for Secret<S, FromStrategy>
where
    FromStrategy: crate::Strategy<S>,
    ToStrategy: crate::Strategy<S>,
{
    type Output = Secret<S, ToStrategy>;

    fn switch_strategy(self) -> Self::Output {
        Secret::new(self.inner_secret)
    }
}


// Function: build_connections
// File: crates/external_services/src/grpc_client/unified_connector_service.rs
// Module: external_services

// Documentation: Builds the connection to the gRPC service

pub fn build_connections(config: &GrpcClientSettings) -> Option<Self>


// Implementation: impl api::PayoutFulfill for for Adyenplatform
// File: crates/hyperswitch_connectors/src/connectors/adyenplatform.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PayoutFulfill for for Adyenplatform


// Function: get_secret
// File: crates/router/src/types/domain/user.rs
// Module: router

pub fn get_secret(&self) -> String


// Function: trigger_refund_execute_workflow
// File: crates/router/src/core/refunds.rs
// Module: router

pub fn trigger_refund_execute_workflow(
    state: &SessionState,
    refund_tracker: &storage::ProcessTracker,
) -> Result<(), errors::ProcessTrackerError>


// Trait: RequestBuilder
// File: crates/router/src/services/api/client.rs
// Module: router

pub trait RequestBuilder: Send + Sync


// Struct: FiservemeaOrder
// File: crates/hyperswitch_connectors/src/connectors/fiservemea/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct FiservemeaOrder


// Function: set_surcharge_details
// File: crates/hyperswitch_domain_models/src/payments/payment_attempt.rs
// Module: hyperswitch_domain_models

pub fn set_surcharge_details(
        &mut self,
        surcharge_details: Option<router_request_types::SurchargeDetails>,
    )


// File: crates/router/src/services/api/client.rs
// Module: router
// Public functions: 2
// Public structs: 3

use std::time::Duration;

use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
pub use external_services::http_client::{self, client};
use http::{HeaderValue, Method};
use hyperswitch_interfaces::types::Proxy;
use masking::PeekInterface;
use reqwest::multipart::Form;
use router_env::tracing_actix_web::RequestId;

use super::{request::Maskable, Request};
use crate::{
    core::errors::{ApiClientError, CustomResult},
    routes::SessionState,
};

pub trait RequestBuilder: Send + Sync {
    fn json(&mut self, body: serde_json::Value);
    fn url_encoded_form(&mut self, body: serde_json::Value);
    fn timeout(&mut self, timeout: Duration);
    fn multipart(&mut self, form: Form);
    fn header(&mut self, key: String, value: Maskable<String>) -> CustomResult<(), ApiClientError>;
    fn send(
        self,
    ) -> CustomResult<
        Box<dyn core::future::Future<Output = Result<reqwest::Response, reqwest::Error>> + 'static>,
        ApiClientError,
    >;
}

#[async_trait::async_trait]
pub trait ApiClient: dyn_clone::DynClone
where
    Self: Send + Sync,
{
    fn request(
        &self,
        method: Method,
        url: String,
    ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError>;

    fn request_with_certificate(
        &self,
        method: Method,
        url: String,
        certificate: Option<masking::Secret<String>>,
        certificate_key: Option<masking::Secret<String>>,
    ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError>;

    async fn send_request(
        &self,
        state: &SessionState,
        request: Request,
        option_timeout_secs: Option<u64>,
        forward_to_kafka: bool,
    ) -> CustomResult<reqwest::Response, ApiClientError>;

    fn add_request_id(&mut self, request_id: RequestId);

    fn get_request_id(&self) -> Option<String>;

    fn add_flow_name(&mut self, flow_name: String);
}

dyn_clone::clone_trait_object!(ApiClient);

#[derive(Clone)]
pub struct ProxyClient {
    proxy_config: Proxy,
    client: reqwest::Client,
    request_id: Option<String>,
}

impl ProxyClient {
    pub fn new(proxy_config: &Proxy) -> CustomResult<Self, ApiClientError> {
        let client = client::get_client_builder(proxy_config)
            .switch()?
            .build()
            .change_context(ApiClientError::InvalidProxyConfiguration)?;
        Ok(Self {
            proxy_config: proxy_config.clone(),
            client,
            request_id: None,
        })
    }

    pub fn get_reqwest_client(
        &self,
        client_certificate: Option<masking::Secret<String>>,
        client_certificate_key: Option<masking::Secret<String>>,
    ) -> CustomResult<reqwest::Client, ApiClientError> {
        match (client_certificate, client_certificate_key) {
            (Some(certificate), Some(certificate_key)) => {
                let client_builder = client::get_client_builder(&self.proxy_config).switch()?;
                let identity =
                    client::create_identity_from_certificate_and_key(certificate, certificate_key)
                        .switch()?;
                Ok(client_builder
                    .identity(identity)
                    .build()
                    .change_context(ApiClientError::ClientConstructionFailed)
                    .attach_printable(
                        "Failed to construct client with certificate and certificate key",
                    )?)
            }
            (_, _) => Ok(self.client.clone()),
        }
    }
}

pub struct RouterRequestBuilder {
    // Using option here to get around the reinitialization problem
    // request builder follows a chain pattern where the value is consumed and a newer requestbuilder is returned
    // Since for this brief period of time between the value being consumed & newer request builder
    // since requestbuilder does not allow moving the value
    // leaves our struct in an inconsistent state, we are using option to get around rust semantics
    inner: Option<reqwest::RequestBuilder>,
}

impl RequestBuilder for RouterRequestBuilder {
    fn json(&mut self, body: serde_json::Value) {
        self.inner = self.inner.take().map(|r| r.json(&body));
    }
    fn url_encoded_form(&mut self, body: serde_json::Value) {
        self.inner = self.inner.take().map(|r| r.form(&body));
    }

    fn timeout(&mut self, timeout: Duration) {
        self.inner = self.inner.take().map(|r| r.timeout(timeout));
    }

    fn multipart(&mut self, form: Form) {
        self.inner = self.inner.take().map(|r| r.multipart(form));
    }

    fn header(&mut self, key: String, value: Maskable<String>) -> CustomResult<(), ApiClientError> {
        let header_value = match value {
            Maskable::Masked(hvalue) => HeaderValue::from_str(hvalue.peek()).map(|mut h| {
                h.set_sensitive(true);
                h
            }),
            Maskable::Normal(hvalue) => HeaderValue::from_str(&hvalue),
        }
        .change_context(ApiClientError::HeaderMapConstructionFailed)?;

        self.inner = self.inner.take().map(|r| r.header(key, header_value));
        Ok(())
    }

    fn send(
        self,
    ) -> CustomResult<
        Box<dyn core::future::Future<Output = Result<reqwest::Response, reqwest::Error>> + 'static>,
        ApiClientError,
    > {
        Ok(Box::new(
            self.inner.ok_or(ApiClientError::UnexpectedState)?.send(),
        ))
    }
}

#[async_trait::async_trait]
impl ApiClient for ProxyClient {
    fn request(
        &self,
        method: Method,
        url: String,
    ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError> {
        self.request_with_certificate(method, url, None, None)
    }

    fn request_with_certificate(
        &self,
        method: Method,
        url: String,
        certificate: Option<masking::Secret<String>>,
        certificate_key: Option<masking::Secret<String>>,
    ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError> {
        let client_builder = self
            .get_reqwest_client(certificate, certificate_key)
            .change_context(ApiClientError::ClientConstructionFailed)?;
        Ok(Box::new(RouterRequestBuilder {
            inner: Some(client_builder.request(method, url)),
        }))
    }
    async fn send_request(
        &self,
        state: &SessionState,
        request: Request,
        option_timeout_secs: Option<u64>,
        _forward_to_kafka: bool,
    ) -> CustomResult<reqwest::Response, ApiClientError> {
        http_client::send_request(&state.conf.proxy, request, option_timeout_secs)
            .await
            .switch()
    }

    fn add_request_id(&mut self, request_id: RequestId) {
        self.request_id
            .replace(request_id.as_hyphenated().to_string());
    }

    fn get_request_id(&self) -> Option<String> {
        self.request_id.clone()
    }

    fn add_flow_name(&mut self, _flow_name: String) {}
}

/// Api client for testing sending request
#[derive(Clone)]
pub struct MockApiClient;

#[async_trait::async_trait]
impl ApiClient for MockApiClient {
    fn request(
        &self,
        _method: Method,
        _url: String,
    ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError> {
        // [#2066]: Add Mock implementation for ApiClient
        Err(ApiClientError::UnexpectedState.into())
    }

    fn request_with_certificate(
        &self,
        _method: Method,
        _url: String,
        _certificate: Option<masking::Secret<String>>,
        _certificate_key: Option<masking::Secret<String>>,
    ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError> {
        // [#2066]: Add Mock implementation for ApiClient
        Err(ApiClientError::UnexpectedState.into())
    }

    async fn send_request(
        &self,
        _state: &SessionState,
        _request: Request,
        _option_timeout_secs: Option<u64>,
        _forward_to_kafka: bool,
    ) -> CustomResult<reqwest::Response, ApiClientError> {
        // [#2066]: Add Mock implementation for ApiClient
        Err(ApiClientError::UnexpectedState.into())
    }

    fn add_request_id(&mut self, _request_id: RequestId) {
        // [#2066]: Add Mock implementation for ApiClient
    }

    fn get_request_id(&self) -> Option<String> {
        // [#2066]: Add Mock implementation for ApiClient
        None
    }

    fn add_flow_name(&mut self, _flow_name: String) {}
}


// Struct: Http400RequestDetailsLoggerMiddleware
// File: crates/router/src/middleware.rs
// Module: router
// Implementations: 0

pub struct Http400RequestDetailsLoggerMiddleware<S>


// File: crates/router/src/routes/webhook_events.rs
// Module: router
// Public functions: 4

use actix_web::{web, HttpRequest, Responder};
use router_env::{instrument, tracing, Flow};

use crate::{
    core::{api_locking, webhooks::webhook_events},
    routes::AppState,
    services::{
        api,
        authentication::{self as auth, UserFromToken},
        authorization::permissions::Permission,
    },
    types::api::webhook_events::{
        EventListConstraints, EventListRequestInternal, WebhookDeliveryAttemptListRequestInternal,
        WebhookDeliveryRetryRequestInternal,
    },
};

#[instrument(skip_all, fields(flow = ?Flow::WebhookEventInitialDeliveryAttemptList))]
pub async fn list_initial_webhook_delivery_attempts(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
    json_payload: web::Json<EventListConstraints>,
) -> impl Responder {
    let flow = Flow::WebhookEventInitialDeliveryAttemptList;
    let merchant_id = path.into_inner();
    let constraints = json_payload.into_inner();

    let request_internal = EventListRequestInternal {
        merchant_id: merchant_id.clone(),
        constraints,
    };

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        request_internal,
        |state, _, request_internal, _| {
            webhook_events::list_initial_delivery_attempts(
                state,
                request_internal.merchant_id,
                request_internal.constraints,
            )
        },
        auth::auth_type(
            &auth::AdminApiAuth,
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                required_permission: Permission::MerchantWebhookEventRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[instrument(skip_all, fields(flow = ?Flow::WebhookEventInitialDeliveryAttemptList))]
pub async fn list_initial_webhook_delivery_attempts_with_jwtauth(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<EventListConstraints>,
) -> impl Responder {
    let flow = Flow::WebhookEventInitialDeliveryAttemptList;
    let constraints = json_payload.into_inner();

    let request_internal = EventListRequestInternal {
        merchant_id: common_utils::id_type::MerchantId::default(),
        constraints,
    };

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        request_internal,
        |state, auth: UserFromToken, mut request_internal, _| {
            let merchant_id = auth.merchant_id;
            let profile_id = auth.profile_id;

            request_internal.merchant_id = merchant_id;
            request_internal.constraints.profile_id = Some(profile_id);

            webhook_events::list_initial_delivery_attempts(
                state,
                request_internal.merchant_id,
                request_internal.constraints,
            )
        },
        &auth::JWTAuth {
            permission: Permission::ProfileWebhookEventRead,
        },
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[instrument(skip_all, fields(flow = ?Flow::WebhookEventDeliveryAttemptList))]
pub async fn list_webhook_delivery_attempts(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<(common_utils::id_type::MerchantId, String)>,
) -> impl Responder {
    let flow = Flow::WebhookEventDeliveryAttemptList;
    let (merchant_id, initial_attempt_id) = path.into_inner();

    let request_internal = WebhookDeliveryAttemptListRequestInternal {
        merchant_id: merchant_id.clone(),
        initial_attempt_id,
    };

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        request_internal,
        |state, _, request_internal, _| {
            webhook_events::list_delivery_attempts(
                state,
                request_internal.merchant_id,
                request_internal.initial_attempt_id,
            )
        },
        auth::auth_type(
            &auth::AdminApiAuth,
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                required_permission: Permission::MerchantWebhookEventRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[instrument(skip_all, fields(flow = ?Flow::WebhookEventDeliveryRetry))]
#[cfg(feature = "v1")]
pub async fn retry_webhook_delivery_attempt(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<(common_utils::id_type::MerchantId, String)>,
) -> impl Responder {
    let flow = Flow::WebhookEventDeliveryRetry;
    let (merchant_id, event_id) = path.into_inner();

    let request_internal = WebhookDeliveryRetryRequestInternal {
        merchant_id: merchant_id.clone(),
        event_id,
    };

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        request_internal,
        |state, _, request_internal, _| {
            webhook_events::retry_delivery_attempt(
                state,
                request_internal.merchant_id,
                request_internal.event_id,
            )
        },
        auth::auth_type(
            &auth::AdminApiAuth,
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                required_permission: Permission::MerchantWebhookEventWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


// Struct: StripeConnectRecipientAccountCreateResponse
// File: crates/hyperswitch_connectors/src/connectors/stripe/transformers/connect.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct StripeConnectRecipientAccountCreateResponse


// Implementation: impl ConnectorValidation for for Worldpayvantiv
// File: crates/hyperswitch_connectors/src/connectors/worldpayvantiv.rs
// Module: hyperswitch_connectors
// Methods: 1 total (0 public)

impl ConnectorValidation for for Worldpayvantiv


// File: crates/router/src/workflows/attach_payout_account_workflow.rs
// Module: router
// Public structs: 1

use common_utils::{
    consts::DEFAULT_LOCALE,
    ext_traits::{OptionExt, ValueExt},
};
use scheduler::{
    consumer::{self, workflows::ProcessTrackerWorkflow},
    errors,
};

use crate::{
    core::payouts,
    errors as core_errors,
    routes::SessionState,
    types::{api, domain, storage},
};

pub struct AttachPayoutAccountWorkflow;

#[async_trait::async_trait]
impl ProcessTrackerWorkflow<SessionState> for AttachPayoutAccountWorkflow {
    async fn execute_workflow<'a>(
        &'a self,
        state: &'a SessionState,
        process: storage::ProcessTracker,
    ) -> Result<(), errors::ProcessTrackerError> {
        // Gather context
        let db = &*state.store;
        let tracking_data: api::PayoutRetrieveRequest = process
            .tracking_data
            .clone()
            .parse_value("PayoutRetrieveRequest")?;

        let merchant_id = tracking_data
            .merchant_id
            .clone()
            .get_required_value("merchant_id")?;
        let key_manager_state = &state.into();
        let key_store = db
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &merchant_id,
                &db.get_master_key().to_vec().into(),
            )
            .await?;

        let merchant_account = db
            .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store)
            .await?;

        let request = api::payouts::PayoutRequest::PayoutRetrieveRequest(tracking_data);

        let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(domain::Context(
            merchant_account.clone(),
            key_store.clone(),
        )));
        let mut payout_data = Box::pin(payouts::make_payout_data(
            state,
            &merchant_context,
            None,
            &request,
            DEFAULT_LOCALE,
        ))
        .await?;

        payouts::payouts_core(state, &merchant_context, &mut payout_data, None, None).await?;

        Ok(())
    }

    async fn error_handler<'a>(
        &'a self,
        state: &'a SessionState,
        process: storage::ProcessTracker,
        error: errors::ProcessTrackerError,
    ) -> core_errors::CustomResult<(), errors::ProcessTrackerError> {
        consumer::consumer_error_handler(state.store.as_scheduler(), process, error).await
    }
}


// Module Structure
// File: crates/hyperswitch_connectors/src/connectors/itaubank.rs
// Module: hyperswitch_connectors

// Public submodules:
pub mod transformers;


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

pub struct PaymentMethodId


// Struct: ProfileSetter
// File: crates/hyperswitch_domain_models/src/business_profile.rs
// Module: hyperswitch_domain_models
// Implementations: 0

pub struct ProfileSetter


// Implementation: impl RoutingAlgorithmUpdate
// File: crates/router/src/core/routing.rs
// Module: router
// Methods: 2 total (1 public)

impl RoutingAlgorithmUpdate


// Implementation: impl DynamicRoutingAlgoAccessor for for ContractRoutingAlgorithm
// File: crates/api_models/src/routing.rs
// Module: api_models
// Methods: 2 total (0 public)

impl DynamicRoutingAlgoAccessor for for ContractRoutingAlgorithm


// File: crates/analytics/src/active_payments/core.rs
// Module: analytics
// Public functions: 1

use std::collections::HashMap;

use api_models::analytics::{
    active_payments::{
        ActivePaymentsMetrics, ActivePaymentsMetricsBucketIdentifier, MetricsBucketResponse,
    },
    AnalyticsMetadata, GetActivePaymentsMetricRequest, MetricsResponse,
};
use error_stack::ResultExt;
use router_env::{instrument, logger, tracing};

use super::ActivePaymentsMetricsAccumulator;
use crate::{
    active_payments::ActivePaymentsMetricAccumulator,
    errors::{AnalyticsError, AnalyticsResult},
    AnalyticsProvider,
};

#[instrument(skip_all)]
pub async fn get_metrics(
    pool: &AnalyticsProvider,
    publishable_key: &String,
    merchant_id: &common_utils::id_type::MerchantId,
    req: GetActivePaymentsMetricRequest,
) -> AnalyticsResult<MetricsResponse<MetricsBucketResponse>> {
    let mut metrics_accumulator: HashMap<
        ActivePaymentsMetricsBucketIdentifier,
        ActivePaymentsMetricsAccumulator,
    > = HashMap::new();

    let mut set = tokio::task::JoinSet::new();
    for metric_type in req.metrics.iter().cloned() {
        let publishable_key_scoped = publishable_key.to_owned();
        let merchant_id_scoped = merchant_id.to_owned();
        let pool = pool.clone();
        set.spawn(async move {
            let data = pool
                .get_active_payments_metrics(
                    &metric_type,
                    &merchant_id_scoped,
                    &publishable_key_scoped,
                    &req.time_range,
                )
                .await
                .change_context(AnalyticsError::UnknownError);
            (metric_type, data)
        });
    }

    while let Some((metric, data)) = set
        .join_next()
        .await
        .transpose()
        .change_context(AnalyticsError::UnknownError)?
    {
        logger::info!("Logging metric: {metric} Result: {:?}", data);
        for (id, value) in data? {
            let metrics_builder = metrics_accumulator.entry(id).or_default();
            match metric {
                ActivePaymentsMetrics::ActivePayments => {
                    metrics_builder.active_payments.add_metrics_bucket(&value)
                }
            }
        }

        logger::debug!(
            "Analytics Accumulated Results: metric: {}, results: {:#?}",
            metric,
            metrics_accumulator
        );
    }

    let query_data: Vec<MetricsBucketResponse> = metrics_accumulator
        .into_iter()
        .map(|(id, val)| MetricsBucketResponse {
            values: val.collect(),
            dimensions: id,
        })
        .collect();

    Ok(MetricsResponse {
        query_data,
        meta_data: [AnalyticsMetadata {
            current_time_range: req.time_range,
        }],
    })
}


// Struct: MifinityPaymentsResponse
// File: crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct MifinityPaymentsResponse


// Module Structure
// File: crates/router/src/core/authentication.rs
// Module: router

// Public submodules:
pub mod transformers;
pub mod types;


// Function: trigger_payouts_webhook
// File: crates/router/src/utils.rs
// Module: router

pub fn trigger_payouts_webhook(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payout_response: &api_models::payouts::PayoutCreateResponse,
) -> RouterResult<()>