pub mod qr_code;
pub mod xml_utils;
use crate::{ConnectorError, IntegrationError};
use base64::Engine;
use common_utils::{
    consts::{
        BASE64_ENGINE, BASE64_ENGINE_STD_NO_PAD, BASE64_ENGINE_URL_SAFE,
        BASE64_ENGINE_URL_SAFE_NO_PAD,
    },
    errors::{ParsingError, ReportSwitchExt},
    ext_traits::ValueExt,
    request::MultipartData,
    types::MinorUnit,
    CustomResult,
};
use domain_types::{
    connector_types::{
        CaptureSyncResponse, PaymentVoidData, PaymentsAuthorizeData, PaymentsCaptureData,
        PaymentsSyncData, RepeatPaymentData, ResponseId, SetupMandateRequestData,
    },
    errors,
    payment_method_data::PaymentMethodDataTypes,
    router_data::ErrorResponse,
    router_response_types::Response,
};
use error_stack::{Report, ResultExt};
use hyperswitch_masking::{ExposeInterface, PeekInterface, Secret};
use serde_json::Value;
use std::{collections::HashMap, str::FromStr};
pub use xml_utils::preprocess_xml_response_bytes;

type Error = Report<IntegrationError>;
use common_enums::enums;
use serde::{Deserialize, Serialize};

pub fn build_form_from_struct<T: Serialize>(
    data: T,
) -> Result<MultipartData, errors::ParsingError> {
    let mut form = MultipartData::new();
    let serialized =
        serde_json::to_value(&data).map_err(|_| errors::ParsingError::EncodeError("json-value"))?;
    let serialized_object = serialized
        .as_object()
        .ok_or(errors::ParsingError::EncodeError("Expected object"))?;
    for (key, values) in serialized_object {
        let value = match values {
            Value::String(s) => s.clone(),
            Value::Number(n) => n.to_string(),
            Value::Bool(b) => b.to_string(),
            Value::Array(_) | Value::Object(_) | Value::Null => {
                tracing::warn!(field = %key, "Form construction encountered a non-primitive type. Skipping field or using empty string.");
                "".to_string()
            }
        };
        form.add_text(key.clone(), value.clone());
    }
    Ok(form)
}

#[macro_export]
macro_rules! with_error_response_body {
    ($event_builder:ident, $response:ident) => {
        if let Some(body) = $event_builder {
            body.set_connector_response(&$response);
        }
    };
}

#[macro_export]
macro_rules! with_response_body {
    ($event_builder:ident, $response:ident) => {
        if let Some(body) = $event_builder {
            body.set_connector_response(&$response);
        }
    };
}

pub trait PaymentsAuthorizeRequestData {
    fn get_router_return_url(&self) -> Result<String, Error>;
}

impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static>
    PaymentsAuthorizeRequestData for PaymentsAuthorizeData<T>
{
    fn get_router_return_url(&self) -> Result<String, Error> {
        self.router_return_url
            .clone()
            .ok_or_else(missing_field_err("return_url"))
    }
}

/// Exposes the requested [`PaymentMethodType`] across the different payment flow request types.
///
/// Flows that do not carry a payment method type (e.g. capture and void) return `None`, so
/// connectors can uniformly read the payment method type in generic response handlers.
pub trait GetOptionalPaymentMethodType {
    fn get_optional_payment_method_type(&self) -> Option<common_enums::PaymentMethodType>;
}

impl<T: PaymentMethodDataTypes> GetOptionalPaymentMethodType for PaymentsAuthorizeData<T> {
    fn get_optional_payment_method_type(&self) -> Option<common_enums::PaymentMethodType> {
        self.payment_method_type
    }
}

impl GetOptionalPaymentMethodType for PaymentsSyncData {
    fn get_optional_payment_method_type(&self) -> Option<common_enums::PaymentMethodType> {
        self.payment_method_type
    }
}

impl<T: PaymentMethodDataTypes> GetOptionalPaymentMethodType for RepeatPaymentData<T> {
    fn get_optional_payment_method_type(&self) -> Option<common_enums::PaymentMethodType> {
        self.payment_method_type
    }
}

impl GetOptionalPaymentMethodType for PaymentsCaptureData {
    fn get_optional_payment_method_type(&self) -> Option<common_enums::PaymentMethodType> {
        None
    }
}

impl GetOptionalPaymentMethodType for PaymentVoidData {
    fn get_optional_payment_method_type(&self) -> Option<common_enums::PaymentMethodType> {
        None
    }
}

pub fn missing_field_err(
    message: &'static str,
) -> Box<dyn Fn() -> Report<IntegrationError> + 'static> {
    Box::new(move || {
        IntegrationError::MissingRequiredField {
            field_name: message,
            context: Default::default(),
        }
        .into()
    })
}

/// Build [`errors::IntegrationErrorContext`] with explicit `additional_context` and `suggested_action`
/// at the call site (no hidden defaults).
pub fn integration_ctx(
    additional_context: impl Into<String>,
    suggested_action: impl Into<String>,
) -> errors::IntegrationErrorContext {
    errors::IntegrationErrorContext {
        additional_context: Some(additional_context.into()),
        suggested_action: Some(suggested_action.into()),
        ..Default::default()
    }
}

/// Use at `.convert(minor_amount, currency)` failures. `flow` should name the **payment operation**
pub fn amount_conversion_ctx(
    flow: &'static str,
    minor_amount: &impl std::fmt::Debug,
    currency: &impl std::fmt::Display,
) -> errors::IntegrationErrorContext {
    integration_ctx(
        format!("{flow}: amount={minor_amount:?}, currency={currency}"),
        "Send the amount in minor units for that ISO currency (e.g. cents for USD), ensure the currency is enabled for this connector, and keep the value within the connector's allowed range."
            .to_string(),
    )
}

// --- Response phase (`ConnectorError`) -----------------

pub fn response_handling_fail(http_status: u16, detail: impl Into<String>) -> ConnectorError {
    ConnectorError::response_handling_failed_with_context(http_status, Some(detail.into()))
}

/// Canonical detail prefix for response-handling failures where connector
/// returned a non-success HTTP status.
pub fn response_http_status_detail(connector: &str) -> String {
    format!(
        "{connector}: connector returned an error HTTP status; check the payment or refund in the connector dashboard."
    )
}

/// Convenience helper for the common non-success HTTP status case.
pub fn response_handling_fail_for_connector(http_status: u16, connector: &str) -> ConnectorError {
    response_handling_fail(http_status, response_http_status_detail(connector))
}

/// Response bytes could not be parsed into the expected response type (JSON/XML, schema drift).
pub fn response_deserialization_fail(
    http_status: u16,
    detail: impl Into<String>,
) -> ConnectorError {
    ConnectorError::response_deserialization_failed_with_context(http_status, Some(detail.into()))
}

/// Connector returned a response that does not match this flow’s contract.
pub fn unexpected_response_fail(http_status: u16, detail: impl Into<String>) -> ConnectorError {
    ConnectorError::unexpected_response_error_with_context(http_status, Some(detail.into()))
}

pub(crate) fn get_unimplemented_payment_method_error_message(connector: &str) -> String {
    format!("Selected payment method through {connector}")
}

pub(crate) fn to_connector_meta_from_secret<T>(
    connector_meta: Option<Secret<Value>>,
) -> Result<T, Error>
where
    T: serde::de::DeserializeOwned,
{
    let connector_meta_secret =
        connector_meta.ok_or_else(missing_field_err("connector_meta_data"))?;

    let json_value = connector_meta_secret.expose();

    let parsed: T = match json_value {
        Value::String(json_str) => serde_json::from_str(&json_str)
            .map_err(Report::from)
            .change_context(IntegrationError::InvalidConnectorConfig {
                config: "merchant_connector_account.metadata",
                context: Default::default(),
            })?,
        _ => serde_json::from_value(json_value.clone())
            .map_err(Report::from)
            .change_context(IntegrationError::InvalidConnectorConfig {
                config: "merchant_connector_account.metadata",
                context: Default::default(),
            })?,
    };

    Ok(parsed)
}

pub(crate) fn handle_json_response_deserialization_failure(
    res: Response,
    _connector: &'static str,
) -> CustomResult<ErrorResponse, ConnectorError> {
    let response_data =
        String::from_utf8(res.response.to_vec()).change_context(response_deserialization_fail(
            res.status_code,
            "Response body was not valid UTF-8 when reading connector error payload.",
        ))?;

    // check for whether the response is in json format
    match serde_json::from_str::<Value>(&response_data) {
        // in case of unexpected response but in json format
        Ok(_) => Err(response_deserialization_fail(
            res.status_code,
            "Connector returned JSON but not in the expected error shape for this handler.",
        ))?,
        // in case of unexpected response but in html or string format
        Err(_error_msg) => Ok(ErrorResponse {
            status_code: res.status_code,
            code: "No error code".to_string(),
            message: "Unsupported response type".to_string(),
            reason: Some(response_data),
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
        }),
    }
}

pub fn is_refund_failure(status: enums::RefundStatus) -> bool {
    match status {
        common_enums::RefundStatus::Failure | common_enums::RefundStatus::TransactionFailure => {
            true
        }
        common_enums::RefundStatus::ManualReview
        | common_enums::RefundStatus::Pending
        | common_enums::RefundStatus::Success
        | common_enums::RefundStatus::Unknown => false,
    }
}

pub(crate) fn safe_base64_decode(base64_data: String) -> Result<Vec<u8>, Error> {
    let mut error_stack = Vec::new();
    [
        &BASE64_ENGINE,
        &BASE64_ENGINE_STD_NO_PAD,
        &BASE64_ENGINE_URL_SAFE,
        &BASE64_ENGINE_URL_SAFE_NO_PAD,
    ]
    .iter()
    .find_map(|engine| {
        engine
            .decode(&base64_data)
            .map_err(|e| error_stack.push(e))
            .ok()
    })
    .ok_or(IntegrationError::InvalidDataFormat {
        field_name: "base64_data",
        context: Default::default(),
    })
    .attach_printable(format!(
        "Base64 decoding failed for all engines. Errors: {:?}",
        error_stack
    ))
}

pub fn deserialize_zero_minor_amount_as_none<'de, D>(
    deserializer: D,
) -> Result<Option<MinorUnit>, D::Error>
where
    D: serde::de::Deserializer<'de>,
{
    let amount = Option::<MinorUnit>::deserialize(deserializer)?;
    match amount {
        Some(value) if value.get_amount_as_i64() == 0 => Ok(None),
        _ => Ok(amount),
    }
}

pub fn convert_uppercase<'de, D, T>(v: D) -> Result<T, D::Error>
where
    D: serde::Deserializer<'de>,
    T: FromStr,
    <T as FromStr>::Err: std::fmt::Debug + std::fmt::Display + std::error::Error,
{
    use serde::de::Error;
    let output = <&str>::deserialize(v)?;
    output.to_uppercase().parse::<T>().map_err(D::Error::custom)
}

pub trait SplitPaymentData {
    fn get_split_payment_data(&self)
        -> Option<domain_types::connector_types::SplitPaymentsDetails>;
}

impl SplitPaymentData for PaymentsCaptureData {
    fn get_split_payment_data(
        &self,
    ) -> Option<domain_types::connector_types::SplitPaymentsDetails> {
        self.split_payments.clone()
    }
}

impl<T: PaymentMethodDataTypes> SplitPaymentData for PaymentsAuthorizeData<T> {
    fn get_split_payment_data(
        &self,
    ) -> Option<domain_types::connector_types::SplitPaymentsDetails> {
        self.split_payments.clone()
    }
}

impl<T: PaymentMethodDataTypes> SplitPaymentData for RepeatPaymentData<T> {
    fn get_split_payment_data(
        &self,
    ) -> Option<domain_types::connector_types::SplitPaymentsDetails> {
        self.split_payments.clone()
    }
}

impl SplitPaymentData for PaymentsSyncData {
    fn get_split_payment_data(
        &self,
    ) -> Option<domain_types::connector_types::SplitPaymentsDetails> {
        self.split_payments.clone()
    }
}

impl SplitPaymentData for PaymentVoidData {
    fn get_split_payment_data(
        &self,
    ) -> Option<domain_types::connector_types::SplitPaymentsDetails> {
        self.split_payments.clone()
    }
}

impl<T: PaymentMethodDataTypes> SplitPaymentData for SetupMandateRequestData<T> {
    fn get_split_payment_data(
        &self,
    ) -> Option<domain_types::connector_types::SplitPaymentsDetails> {
        self.split_payments.clone()
    }
}

pub fn serialize_to_xml_string_with_root<T: Serialize>(
    root_name: &str,
    data: &T,
) -> Result<String, Error> {
    let xml_content = quick_xml::se::to_string_with_root(root_name, data)
        .change_context(IntegrationError::RequestEncodingFailed {
            context: Default::default(),
        })
        .attach_printable("Failed to serialize XML with root")?;

    let full_xml = format!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>{xml_content}");
    Ok(full_xml)
}

pub fn get_error_code_error_message_based_on_priority(
    connector: impl ConnectorErrorTypeMapping,
    error_list: Vec<ErrorCodeAndMessage>,
) -> Option<ErrorCodeAndMessage> {
    let error_type_list = error_list
        .iter()
        .map(|error| {
            connector
                .get_connector_error_type(error.error_code.clone(), error.error_message.clone())
        })
        .collect::<Vec<ConnectorErrorType>>();
    let mut error_zip_list = error_list
        .iter()
        .zip(error_type_list.iter())
        .collect::<Vec<(&ErrorCodeAndMessage, &ConnectorErrorType)>>();
    error_zip_list.sort_by_key(|&(_, error_type)| error_type);
    error_zip_list
        .first()
        .map(|&(error_code_message, _)| error_code_message)
        .cloned()
}

pub trait ConnectorErrorTypeMapping {
    fn get_connector_error_type(
        &self,
        _error_code: String,
        _error_message: String,
    ) -> ConnectorErrorType {
        ConnectorErrorType::UnknownError
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ErrorCodeAndMessage {
    pub error_code: String,
    pub error_message: String,
}

#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
//Priority of connector_error_type
pub enum ConnectorErrorType {
    UserError = 2,
    BusinessError = 3,
    TechnicalError = 4,
    UnknownError = 1,
}

pub(crate) fn to_connector_meta<T>(connector_meta: Option<Value>) -> Result<T, Error>
where
    T: serde::de::DeserializeOwned,
{
    let json = connector_meta.ok_or_else(missing_field_err("connector_meta_data"))?;
    json.parse_value(std::any::type_name::<T>()).switch()
}

pub trait MultipleCaptureSyncResponse {
    fn get_connector_capture_id(&self) -> String;
    fn get_capture_attempt_status(&self) -> common_enums::AttemptStatus;
    fn is_capture_response(&self) -> bool;
    fn get_connector_reference_id(&self) -> Option<String> {
        None
    }
    fn get_amount_captured(&self) -> Result<Option<MinorUnit>, Report<ParsingError>>;
}

pub(crate) fn construct_captures_response_hashmap<T>(
    capture_sync_response_list: Vec<T>,
) -> CustomResult<HashMap<String, CaptureSyncResponse>, ConnectorError>
where
    T: MultipleCaptureSyncResponse,
{
    let mut hashmap = HashMap::new();
    for capture_sync_response in capture_sync_response_list {
        let connector_capture_id = capture_sync_response.get_connector_capture_id();
        if capture_sync_response.is_capture_response() {
            hashmap.insert(
                connector_capture_id.clone(),
                CaptureSyncResponse::Success {
                    resource_id: ResponseId::ConnectorTransactionId(connector_capture_id),
                    status: capture_sync_response.get_capture_attempt_status(),
                    connector_response_reference_id: capture_sync_response
                        .get_connector_reference_id(),
                    amount: capture_sync_response
                        .get_amount_captured()
                        .change_context(
                            ConnectorError::response_handling_failed_http_status_unknown(),
                        )
                        .attach_printable(
                            "failed to convert back captured response amount to minor unit",
                        )?,
                },
            );
        }
    }

    Ok(hashmap)
}

pub(crate) fn is_manual_capture(capture_method: Option<enums::CaptureMethod>) -> bool {
    capture_method == Some(enums::CaptureMethod::Manual)
        || capture_method == Some(enums::CaptureMethod::ManualMultiple)
}

pub fn get_token_expiry_month_year_2_digit_with_delimiter(
    month: Secret<String>,
    year: Secret<String>,
) -> Secret<String> {
    let year_2_digit = if year.peek().len() == 4 {
        Secret::new(year.peek().chars().skip(2).collect::<String>())
    } else {
        year
    };
    Secret::new(format!("{}/{}", month.peek(), year_2_digit.peek()))
}

/// Common merchant-defined information structure for Cybersource-based connectors
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MerchantDefinedInformation {
    pub key: u8,
    pub value: String,
}

/// Converts metadata JSON to merchant-defined information format
///
/// Used by Cybersource-based connectors (Barclaycard, Cybersource) to send custom merchant metadata.
/// The silent failure (unwrap_or_default) is intentional:
/// - Metadata is optional and non-critical for payment processing
/// - Input is already valid JSON (serde_json::Value), so parsing rarely fails
/// - Better to continue payment without metadata than to fail the entire payment
pub fn convert_metadata_to_merchant_defined_info(
    metadata: Value,
) -> Vec<MerchantDefinedInformation> {
    serde_json::from_str::<std::collections::BTreeMap<String, Value>>(&metadata.to_string())
        .unwrap_or_default()
        .into_iter()
        .enumerate()
        .filter_map(|(index, (key, value))| {
            u8::try_from(index + 1)
                .ok()
                .map(|key_num| MerchantDefinedInformation {
                    key: key_num,
                    value: format!("{key}={value}"),
                })
        })
        .collect()
}

/// Convert state/province to 2-letter code based on country
/// Returns None if the state is already 2 letters or if conversion is not needed
/// Returns Some(code) if successfully converted from full name to abbreviation
pub fn get_state_code_for_country(
    state: &Secret<String>,
    country: Option<common_enums::CountryAlpha2>,
) -> Option<Secret<String>> {
    let state_str = state.peek();

    // If already 2 letters, return as-is (already a code)
    if state_str.len() == 2 {
        Some(state.clone())
    } else if state_str.is_empty() {
        // If empty, return None
        None
    } else {
        // Convert based on country
        match country {
            Some(common_enums::CountryAlpha2::US) => {
                // Try to convert US state name to abbreviation
                common_enums::UsStatesAbbreviation::from_state_name(state_str)
                    .map(|abbr| Secret::new(abbr.to_string()))
            }
            Some(common_enums::CountryAlpha2::CA) => {
                // Try to convert Canada province name to abbreviation
                common_enums::CanadaStatesAbbreviation::from_province_name(state_str)
                    .map(|abbr| Secret::new(abbr.to_string()))
            }
            _ => {
                // For other countries, return the state as-is if it's not empty
                Some(state.clone())
            }
        }
    }
}

/// Utility function for collecting and sorting values from JSON for webhook signature verification.
///
/// Recursively collects all values from a JSON structure, excluding a specific signature field,
/// sorts them alphabetically, and returns them joined with "/" separator.
///
/// # Arguments
/// * `value` - The JSON value to process
/// * `signature` - The signature value to exclude from collection
///
/// # Returns
/// Sorted vector of all values (excluding the signature)
pub fn collect_and_sort_values_by_removing_signature(
    value: &Value,
    signature: &str,
) -> Vec<String> {
    let mut values = collect_values_by_removing_signature(value, signature);
    values.sort();
    values
}

fn collect_values_by_removing_signature(value: &Value, signature: &str) -> Vec<String> {
    match value {
        Value::Null => vec!["null".to_owned()],
        Value::Bool(b) => vec![b.to_string()],
        Value::Number(n) => match n.as_f64() {
            Some(f) => vec![format!("{f:.2}")],
            None => vec![n.to_string()],
        },
        Value::String(s) => {
            if signature == s {
                vec![]
            } else {
                vec![s.clone()]
            }
        }
        Value::Array(arr) => arr
            .iter()
            .flat_map(|v| collect_values_by_removing_signature(v, signature))
            .collect(),
        Value::Object(obj) => obj
            .values()
            .flat_map(|v| collect_values_by_removing_signature(v, signature))
            .collect(),
    }
}

pub fn build_card_holder_name(
    explicit_name: &Option<Secret<String>>,
    billing_first_name: Option<Secret<String>>,
    billing_last_name: Option<Secret<String>>,
) -> Option<Secret<String>> {
    explicit_name.clone().or_else(|| {
        let first = billing_first_name.map(|n| n.expose()).unwrap_or_default();
        let last = billing_last_name.map(|n| n.expose()).unwrap_or_default();
        let full = format!("{first} {last}").trim().to_string();
        if full.is_empty() {
            None
        } else {
            Some(Secret::new(full))
        }
    })
}

/// Card networks (notably Mastercard) require the cardholder name to contain only
/// English (ASCII) characters; accented characters are transliterated to the
/// closest ASCII equivalent.
pub fn normalize_cardholder_name(name: Secret<String>) -> Secret<String> {
    Secret::new(unidecode::unidecode(&name.expose()))
}

pub fn pad_expiry_year_to_four_digits(year: &Secret<String>) -> Secret<String> {
    domain_types::utils::expand_expiry_year_to_four_digits(year)
}

/// Used by CyberSource and connectors that run on the same backend (e.g. Wells Fargo).
pub trait CardTypeCode {
    fn type_code(&self) -> Option<&'static str>;
}

impl CardTypeCode for domain_types::utils::CardIssuer {
    fn type_code(&self) -> Option<&'static str> {
        Some(match self {
            Self::AmericanExpress => "003",
            Self::Master => "002",
            Self::Maestro => "042",
            Self::Visa => "001",
            Self::Discover => "004",
            Self::DinersClub => "005",
            Self::CarteBlanche => "006",
            Self::JCB => "007",
            Self::CartesBancaires => "036",
            Self::UnionPay => "062",
        })
    }
}

impl CardTypeCode for common_enums::CardNetwork {
    fn type_code(&self) -> Option<&'static str> {
        match self {
            Self::Visa => Some("001"),
            Self::Mastercard => Some("002"),
            Self::AmericanExpress => Some("003"),
            Self::Discover => Some("004"),
            Self::DinersClub => Some("005"),
            Self::JCB => Some("007"),
            Self::Maestro => Some("042"),
            Self::CartesBancaires => Some("036"),
            Self::UnionPay => Some("062"),
            _ => None,
        }
    }
}

pub fn truncate_secret_string(value: &Secret<String>, max_len: usize) -> Secret<String> {
    let s = value.peek();
    if s.len() > max_len {
        Secret::new(s.chars().take(max_len).collect())
    } else {
        Secret::new(s.clone())
    }
}

pub fn build_error_response(
    code: String,
    message: String,
    status_code: u16,
    connector_transaction_id: Option<String>,
) -> ErrorResponse {
    ErrorResponse {
        code,
        message: message.clone(),
        reason: Some(message),
        status_code,
        attempt_status: None,
        connector_transaction_id,
        network_decline_code: None,
        network_advice_code: None,
        network_error_message: None,
    }
}
