use std::{fmt, marker::PhantomData};

use api_models::{
    analytics::{
        self as analytics_api,
        api_event::ApiEventDimensions,
        auth_events::{AuthEventDimensions, AuthEventFlows},
        disputes::DisputeDimensions,
        frm::{FrmDimensions, FrmTransactionType},
        payment_intents::PaymentIntentDimensions,
        payments::{PaymentDimensions, PaymentDistributions},
        refunds::{RefundDimensions, RefundDistributions, RefundType},
        sdk_events::{SdkEventDimensions, SdkEventNames},
        Granularity,
    },
    enums::{
        AttemptStatus, AuthenticationType, Connector, Currency, DisputeStage, IntentStatus,
        PaymentMethod, PaymentMethodType, RoutingApproach,
    },
    refunds::RefundStatus,
};
use common_enums::{
    AuthenticationConnectors, AuthenticationStatus, DecoupledAuthenticationType, TransactionStatus,
};
use common_utils::{
    errors::{CustomResult, ParsingError},
    id_type::{MerchantId, OrganizationId, ProfileId},
};
use diesel_models::{enums as storage_enums, enums::FraudCheckStatus};
use error_stack::ResultExt;
use router_env::{logger, Flow};

use super::types::{AnalyticsCollection, AnalyticsDataSource, LoadRow, TableEngine};
use crate::{enums::AuthInfo, types::QueryExecutionError};
pub type QueryResult<T> = error_stack::Result<T, QueryBuildingError>;
pub trait QueryFilter<T>
where
    T: AnalyticsDataSource,
    AnalyticsCollection: ToSql<T>,
{
    fn set_filter_clause(&self, builder: &mut QueryBuilder<T>) -> QueryResult<()>;
}

pub trait GroupByClause<T>
where
    T: AnalyticsDataSource,
    AnalyticsCollection: ToSql<T>,
{
    fn set_group_by_clause(&self, builder: &mut QueryBuilder<T>) -> QueryResult<()>;
}

pub trait SeriesBucket {
    type SeriesType;
    type GranularityLevel;

    fn get_lowest_common_granularity_level(&self) -> Self::GranularityLevel;

    fn get_bucket_size(&self) -> u8;

    fn clip_to_start(
        &self,
        value: Self::SeriesType,
    ) -> error_stack::Result<Self::SeriesType, PostProcessingError>;

    fn clip_to_end(
        &self,
        value: Self::SeriesType,
    ) -> error_stack::Result<Self::SeriesType, PostProcessingError>;
}

impl<T> QueryFilter<T> for analytics_api::TimeRange
where
    T: AnalyticsDataSource,
    time::PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
{
    fn set_filter_clause(&self, builder: &mut QueryBuilder<T>) -> QueryResult<()> {
        builder.add_custom_filter_clause("created_at", self.start_time, FilterTypes::Gte)?;
        if let Some(end) = self.end_time {
            builder.add_custom_filter_clause("created_at", end, FilterTypes::Lte)?;
        }
        Ok(())
    }
}

impl GroupByClause<super::SqlxClient> for Granularity {
    fn set_group_by_clause(
        &self,
        builder: &mut QueryBuilder<super::SqlxClient>,
    ) -> QueryResult<()> {
        let trunc_scale = self.get_lowest_common_granularity_level();

        let granularity_bucket_scale = match self {
            Self::OneMin => None,
            Self::FiveMin | Self::FifteenMin | Self::ThirtyMin => Some("minute"),
            Self::OneHour | Self::OneDay => None,
        };

        let granularity_divisor = self.get_bucket_size();

        builder
            .add_group_by_clause(format!("DATE_TRUNC('{trunc_scale}', created_at)"))
            .attach_printable("Error adding time prune group by")?;
        if let Some(scale) = granularity_bucket_scale {
            builder
                .add_group_by_clause(format!(
                    "FLOOR(DATE_PART('{scale}', created_at)/{granularity_divisor})"
                ))
                .attach_printable("Error adding time binning group by")?;
        }
        Ok(())
    }
}

impl GroupByClause<super::ClickhouseClient> for Granularity {
    fn set_group_by_clause(
        &self,
        builder: &mut QueryBuilder<super::ClickhouseClient>,
    ) -> QueryResult<()> {
        let interval = match self {
            Self::OneMin => "toStartOfMinute(created_at)",
            Self::FiveMin => "toStartOfFiveMinutes(created_at)",
            Self::FifteenMin => "toStartOfFifteenMinutes(created_at)",
            Self::ThirtyMin => "toStartOfInterval(created_at, INTERVAL 30 minute)",
            Self::OneHour => "toStartOfHour(created_at)",
            Self::OneDay => "toStartOfDay(created_at)",
        };

        builder
            .add_group_by_clause(interval)
            .attach_printable("Error adding interval group by")
    }
}

#[derive(strum::Display)]
#[strum(serialize_all = "lowercase")]
pub enum TimeGranularityLevel {
    Minute,
    Hour,
    Day,
}

impl SeriesBucket for Granularity {
    type SeriesType = time::PrimitiveDateTime;

    type GranularityLevel = TimeGranularityLevel;

    fn get_lowest_common_granularity_level(&self) -> Self::GranularityLevel {
        match self {
            Self::OneMin => TimeGranularityLevel::Minute,
            Self::FiveMin | Self::FifteenMin | Self::ThirtyMin | Self::OneHour => {
                TimeGranularityLevel::Hour
            }
            Self::OneDay => TimeGranularityLevel::Day,
        }
    }

    fn get_bucket_size(&self) -> u8 {
        match self {
            Self::OneMin => 60,
            Self::FiveMin => 5,
            Self::FifteenMin => 15,
            Self::ThirtyMin => 30,
            Self::OneHour => 60,
            Self::OneDay => 24,
        }
    }

    fn clip_to_start(
        &self,
        value: Self::SeriesType,
    ) -> error_stack::Result<Self::SeriesType, PostProcessingError> {
        let clip_start = |value: u8, modulo: u8| -> u8 { value - value % modulo };

        let clipped_time = match (
            self.get_lowest_common_granularity_level(),
            self.get_bucket_size(),
        ) {
            (TimeGranularityLevel::Minute, i) => time::Time::MIDNIGHT
                .replace_second(clip_start(value.second(), i))
                .and_then(|t| t.replace_minute(value.minute()))
                .and_then(|t| t.replace_hour(value.hour())),
            (TimeGranularityLevel::Hour, i) => time::Time::MIDNIGHT
                .replace_minute(clip_start(value.minute(), i))
                .and_then(|t| t.replace_hour(value.hour())),
            (TimeGranularityLevel::Day, i) => {
                time::Time::MIDNIGHT.replace_hour(clip_start(value.hour(), i))
            }
        }
        .change_context(PostProcessingError::BucketClipping)?;

        Ok(value.replace_time(clipped_time))
    }

    fn clip_to_end(
        &self,
        value: Self::SeriesType,
    ) -> error_stack::Result<Self::SeriesType, PostProcessingError> {
        let clip_end = |value: u8, modulo: u8| -> u8 { value + modulo - 1 - value % modulo };

        let clipped_time = match (
            self.get_lowest_common_granularity_level(),
            self.get_bucket_size(),
        ) {
            (TimeGranularityLevel::Minute, i) => time::Time::MIDNIGHT
                .replace_second(clip_end(value.second(), i))
                .and_then(|t| t.replace_minute(value.minute()))
                .and_then(|t| t.replace_hour(value.hour())),
            (TimeGranularityLevel::Hour, i) => time::Time::MIDNIGHT
                .replace_minute(clip_end(value.minute(), i))
                .and_then(|t| t.replace_hour(value.hour())),
            (TimeGranularityLevel::Day, i) => {
                time::Time::MIDNIGHT.replace_hour(clip_end(value.hour(), i))
            }
        }
        .change_context(PostProcessingError::BucketClipping)
        .attach_printable_lazy(|| format!("Bucket Clip Error: {value}"))?;

        Ok(value.replace_time(clipped_time))
    }
}

#[derive(thiserror::Error, Debug)]
pub enum QueryBuildingError {
    #[allow(dead_code)]
    #[error("Not Implemented: {0}")]
    NotImplemented(String),
    #[error("Failed to Serialize to SQL")]
    SqlSerializeError,
    #[error("Failed to build sql query: {0}")]
    InvalidQuery(&'static str),
}

#[derive(thiserror::Error, Debug)]
pub enum PostProcessingError {
    #[error("Error Clipping values to bucket sizes")]
    BucketClipping,
}

#[derive(Debug)]
pub enum Aggregate<R> {
    Count {
        field: Option<R>,
        alias: Option<&'static str>,
    },
    Sum {
        field: R,
        alias: Option<&'static str>,
    },
    Min {
        field: R,
        alias: Option<&'static str>,
    },
    Max {
        field: R,
        alias: Option<&'static str>,
    },
    Percentile {
        field: R,
        alias: Option<&'static str>,
        percentile: Option<&'static u8>,
    },
    DistinctCount {
        field: R,
        alias: Option<&'static str>,
    },
}

// Window functions in query
// ---
// Description -
// field: to_sql type value used as expr in aggregation
// partition_by: partition by fields in window
// order_by: order by fields and order (Ascending / Descending) in window
// alias: alias of window expr in query
// ---
// Usage -
// Window::Sum {
//     field: "count",
//     partition_by: Some(query_builder.transform_to_sql_values(&dimensions).switch()?),
//     order_by: Some(("value", Descending)),
//     alias: Some("total"),
// }
#[derive(Debug)]
pub enum Window<R> {
    Sum {
        field: R,
        partition_by: Option<String>,
        order_by: Option<(String, Order)>,
        alias: Option<&'static str>,
    },
    RowNumber {
        field: R,
        partition_by: Option<String>,
        order_by: Option<(String, Order)>,
        alias: Option<&'static str>,
    },
}

#[derive(Debug, Clone, Copy)]
pub enum Order {
    Ascending,
    Descending,
}

impl fmt::Display for Order {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Ascending => write!(f, "asc"),
            Self::Descending => write!(f, "desc"),
        }
    }
}

// Select TopN values for a group based on a metric
// ---
// Description -
// columns: Columns in group to select TopN values for
// count: N in TopN
// order_column: metric used to sort and limit TopN
// order: sort order of metric (Ascending / Descending)
// ---
// Usage -
// Use via add_top_n_clause fn of query_builder
// add_top_n_clause(
//     &dimensions,
//     distribution.distribution_cardinality.into(),
//     "count",
//     Order::Descending,
// )
#[allow(dead_code)]
#[derive(Debug)]
pub struct TopN {
    pub columns: String,
    pub count: u64,
    pub order_column: String,
    pub order: Order,
}

#[derive(Debug, Clone)]
pub struct LimitByClause {
    limit: u64,
    columns: Vec<String>,
}

impl fmt::Display for LimitByClause {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "LIMIT {} BY {}", self.limit, self.columns.join(", "))
    }
}

#[derive(Debug, Default, Clone, Copy)]
pub enum FilterCombinator {
    #[default]
    And,
    Or,
}

impl<T: AnalyticsDataSource> ToSql<T> for FilterCombinator {
    fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result<String, ParsingError> {
        Ok(match self {
            Self::And => " AND ",
            Self::Or => " OR ",
        }
        .to_owned())
    }
}

#[derive(Debug, Clone)]
pub enum Filter {
    Plain(String, FilterTypes, String),
    NestedFilter(FilterCombinator, Vec<Self>),
}

impl Default for Filter {
    fn default() -> Self {
        Self::NestedFilter(FilterCombinator::default(), Vec::new())
    }
}

impl<T: AnalyticsDataSource> ToSql<T> for Filter {
    fn to_sql(&self, table_engine: &TableEngine) -> error_stack::Result<String, ParsingError> {
        Ok(match self {
            Self::Plain(l, op, r) => filter_type_to_sql(l, *op, r),
            Self::NestedFilter(operator, filters) => {
                format!(
                    "( {} )",
                    filters
                        .iter()
                        .map(|f| <Self as ToSql<T>>::to_sql(f, table_engine))
                        .collect::<Result<Vec<String>, _>>()?
                        .join(
                            <FilterCombinator as ToSql<T>>::to_sql(operator, table_engine)?
                                .as_ref()
                        )
                )
            }
        })
    }
}

#[derive(Debug)]
pub struct QueryBuilder<T>
where
    T: AnalyticsDataSource,
    AnalyticsCollection: ToSql<T>,
{
    columns: Vec<String>,
    filters: Filter,
    group_by: Vec<String>,
    order_by: Vec<String>,
    having: Option<Vec<(String, FilterTypes, String)>>,
    limit_by: Option<LimitByClause>,
    outer_select: Vec<String>,
    top_n: Option<TopN>,
    table: AnalyticsCollection,
    distinct: bool,
    db_type: PhantomData<T>,
    table_engine: TableEngine,
}

pub trait ToSql<T: AnalyticsDataSource> {
    fn to_sql(&self, table_engine: &TableEngine) -> error_stack::Result<String, ParsingError>;
}

impl<T: AnalyticsDataSource> ToSql<T> for &MerchantId {
    fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result<String, ParsingError> {
        Ok(self.get_string_repr().to_owned())
    }
}

impl<T: AnalyticsDataSource> ToSql<T> for MerchantId {
    fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result<String, ParsingError> {
        Ok(self.get_string_repr().to_owned())
    }
}

impl<T: AnalyticsDataSource> ToSql<T> for &OrganizationId {
    fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result<String, ParsingError> {
        Ok(self.get_string_repr().to_owned())
    }
}

impl<T: AnalyticsDataSource> ToSql<T> for ProfileId {
    fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result<String, ParsingError> {
        Ok(self.get_string_repr().to_owned())
    }
}

impl<T: AnalyticsDataSource> ToSql<T> for &common_utils::id_type::PaymentId {
    fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result<String, ParsingError> {
        Ok(self.get_string_repr().to_owned())
    }
}

impl<T: AnalyticsDataSource> ToSql<T> for &common_utils::id_type::PayoutId {
    fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result<String, ParsingError> {
        Ok(self.get_string_repr().to_owned())
    }
}

impl<T: AnalyticsDataSource> ToSql<T> for common_utils::id_type::CustomerId {
    fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result<String, ParsingError> {
        Ok(self.get_string_repr().to_owned())
    }
}

impl<T: AnalyticsDataSource> ToSql<T> for bool {
    fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result<String, ParsingError> {
        let flag = *self;
        Ok(i8::from(flag).to_string())
    }
}

/// Implement `ToSql` on arrays of types that impl `ToString`.
macro_rules! impl_to_sql_for_to_string {
    ($($type:ty),+) => {
        $(
            impl<T: AnalyticsDataSource> ToSql<T> for $type {
                fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result<String, ParsingError> {
                    Ok(self.to_string())
                }
            }
        )+
     };
}

impl_to_sql_for_to_string!(
    String,
    &str,
    &PaymentDimensions,
    &PaymentIntentDimensions,
    &RefundDimensions,
    &FrmDimensions,
    PaymentDimensions,
    PaymentIntentDimensions,
    &PaymentDistributions,
    RefundDimensions,
    &RefundDistributions,
    FrmDimensions,
    PaymentMethod,
    PaymentMethodType,
    AuthenticationType,
    Connector,
    AttemptStatus,
    IntentStatus,
    RefundStatus,
    FraudCheckStatus,
    storage_enums::RefundStatus,
    Currency,
    RefundType,
    FrmTransactionType,
    TransactionStatus,
    AuthenticationStatus,
    AuthenticationConnectors,
    DecoupledAuthenticationType,
    Flow,
    &String,
    &bool,
    &u64,
    u64,
    Order,
    RoutingApproach
);

impl_to_sql_for_to_string!(
    &SdkEventDimensions,
    SdkEventDimensions,
    SdkEventNames,
    AuthEventFlows,
    &ApiEventDimensions,
    ApiEventDimensions,
    &DisputeDimensions,
    DisputeDimensions,
    DisputeStage,
    AuthEventDimensions,
    &AuthEventDimensions
);

#[derive(Debug, Clone, Copy)]
pub enum FilterTypes {
    Equal,
    NotEqual,
    EqualBool,
    In,
    Gte,
    Lte,
    Gt,
    Like,
    NotLike,
    IsNotNull,
}

pub fn filter_type_to_sql(l: &str, op: FilterTypes, r: &str) -> String {
    match op {
        FilterTypes::EqualBool => format!("{l} = {r}"),
        FilterTypes::Equal => format!("{l} = '{r}'"),
        FilterTypes::NotEqual => format!("{l} != '{r}'"),
        FilterTypes::In => format!("{l} IN ({r})"),
        FilterTypes::Gte => format!("{l} >= '{r}'"),
        FilterTypes::Gt => format!("{l} > {r}"),
        FilterTypes::Lte => format!("{l} <= '{r}'"),
        FilterTypes::Like => format!("{l} LIKE '%{r}%'"),
        FilterTypes::NotLike => format!("{l} NOT LIKE '%{r}%'"),
        FilterTypes::IsNotNull => format!("{l} IS NOT NULL"),
    }
}

impl<T> QueryBuilder<T>
where
    T: AnalyticsDataSource,
    AnalyticsCollection: ToSql<T>,
{
    pub fn new(table: AnalyticsCollection) -> Self {
        Self {
            columns: Default::default(),
            filters: Default::default(),
            group_by: Default::default(),
            order_by: Default::default(),
            having: Default::default(),
            limit_by: Default::default(),
            outer_select: Default::default(),
            top_n: Default::default(),
            table,
            distinct: Default::default(),
            db_type: Default::default(),
            table_engine: T::get_table_engine(table),
        }
    }

    pub fn add_select_column(&mut self, column: impl ToSql<T>) -> QueryResult<()> {
        self.columns.push(
            column
                .to_sql(&self.table_engine)
                .change_context(QueryBuildingError::SqlSerializeError)
                .attach_printable("Error serializing select column")?,
        );
        Ok(())
    }

    pub fn transform_to_sql_values(&mut self, values: &[impl ToSql<T>]) -> QueryResult<String> {
        let res = values
            .iter()
            .map(|i| i.to_sql(&self.table_engine))
            .collect::<error_stack::Result<Vec<String>, ParsingError>>()
            .change_context(QueryBuildingError::SqlSerializeError)
            .attach_printable("Error serializing range filter value")?
            .join(", ");
        Ok(res)
    }

    pub fn add_top_n_clause(
        &mut self,
        columns: &[impl ToSql<T>],
        count: u64,
        order_column: impl ToSql<T>,
        order: Order,
    ) -> QueryResult<()>
    where
        Window<&'static str>: ToSql<T>,
    {
        let partition_by_columns = self.transform_to_sql_values(columns)?;
        let order_by_column = order_column
            .to_sql(&self.table_engine)
            .change_context(QueryBuildingError::SqlSerializeError)
            .attach_printable("Error serializing select column")?;

        self.add_outer_select_column(Window::RowNumber {
            field: "",
            partition_by: Some(partition_by_columns.clone()),
            order_by: Some((order_by_column.clone(), order)),
            alias: Some("top_n"),
        })?;

        self.top_n = Some(TopN {
            columns: partition_by_columns,
            count,
            order_column: order_by_column,
            order,
        });
        Ok(())
    }

    pub fn set_distinct(&mut self) {
        self.distinct = true
    }

    pub fn add_filter_clause(
        &mut self,
        key: impl ToSql<T>,
        value: impl ToSql<T>,
    ) -> QueryResult<()> {
        self.add_custom_filter_clause(key, value, FilterTypes::Equal)
    }

    pub fn add_bool_filter_clause(
        &mut self,
        key: impl ToSql<T>,
        value: impl ToSql<T>,
    ) -> QueryResult<()> {
        self.add_custom_filter_clause(key, value, FilterTypes::EqualBool)
    }

    pub fn add_negative_filter_clause(
        &mut self,
        key: impl ToSql<T>,
        value: impl ToSql<T>,
    ) -> QueryResult<()> {
        self.add_custom_filter_clause(key, value, FilterTypes::NotEqual)
    }

    pub fn add_custom_filter_clause(
        &mut self,
        lhs: impl ToSql<T>,
        rhs: impl ToSql<T>,
        comparison: FilterTypes,
    ) -> QueryResult<()> {
        let filter = Filter::Plain(
            lhs.to_sql(&self.table_engine)
                .change_context(QueryBuildingError::SqlSerializeError)
                .attach_printable("Error serializing filter key")?,
            comparison,
            rhs.to_sql(&self.table_engine)
                .change_context(QueryBuildingError::SqlSerializeError)
                .attach_printable("Error serializing filter value")?,
        );
        self.add_nested_filter_clause(filter);
        Ok(())
    }
    pub fn add_nested_filter_clause(&mut self, filter: Filter) {
        match &mut self.filters {
            Filter::NestedFilter(_, ref mut filters) => filters.push(filter),
            f @ Filter::Plain(_, _, _) => {
                self.filters = Filter::NestedFilter(FilterCombinator::And, vec![f.clone(), filter]);
            }
        }
    }

    pub fn add_filter_in_range_clause(
        &mut self,
        key: impl ToSql<T>,
        values: &[impl ToSql<T>],
    ) -> QueryResult<()> {
        let list = values
            .iter()
            .map(|i| {
                // trimming whitespaces from the filter values received in request, to prevent a possibility of an SQL injection
                i.to_sql(&self.table_engine).map(|s| {
                    let trimmed_str = s.replace(' ', "");
                    format!("'{trimmed_str}'")
                })
            })
            .collect::<error_stack::Result<Vec<String>, ParsingError>>()
            .change_context(QueryBuildingError::SqlSerializeError)
            .attach_printable("Error serializing range filter value")?
            .join(", ");
        self.add_custom_filter_clause(key, list, FilterTypes::In)
    }

    pub fn add_group_by_clause(&mut self, column: impl ToSql<T>) -> QueryResult<()> {
        self.group_by.push(
            column
                .to_sql(&self.table_engine)
                .change_context(QueryBuildingError::SqlSerializeError)
                .attach_printable("Error serializing group by field")?,
        );
        Ok(())
    }

    pub fn add_order_by_clause(
        &mut self,
        column: impl ToSql<T>,
        order: impl ToSql<T>,
    ) -> QueryResult<()> {
        let column_sql = column
            .to_sql(&self.table_engine)
            .change_context(QueryBuildingError::SqlSerializeError)
            .attach_printable("Error serializing order by column")?;

        let order_sql = order
            .to_sql(&self.table_engine)
            .change_context(QueryBuildingError::SqlSerializeError)
            .attach_printable("Error serializing order direction")?;

        self.order_by.push(format!("{column_sql} {order_sql}"));
        Ok(())
    }

    pub fn set_limit_by(&mut self, limit: u64, columns: &[impl ToSql<T>]) -> QueryResult<()> {
        let columns = columns
            .iter()
            .map(|col| col.to_sql(&self.table_engine))
            .collect::<Result<Vec<String>, _>>()
            .change_context(QueryBuildingError::SqlSerializeError)
            .attach_printable("Error serializing LIMIT BY columns")?;

        self.limit_by = Some(LimitByClause { limit, columns });
        Ok(())
    }

    pub fn add_granularity_in_mins(&mut self, granularity: Granularity) -> QueryResult<()> {
        let interval = match granularity {
            Granularity::OneMin => "1",
            Granularity::FiveMin => "5",
            Granularity::FifteenMin => "15",
            Granularity::ThirtyMin => "30",
            Granularity::OneHour => "60",
            Granularity::OneDay => "1440",
        };
        let _ = self.add_select_column(format!(
            "toStartOfInterval(created_at, INTERVAL {interval} MINUTE) as time_bucket"
        ));
        Ok(())
    }

    fn get_filter_clause(&self) -> QueryResult<String> {
        <Filter as ToSql<T>>::to_sql(&self.filters, &self.table_engine)
            .change_context(QueryBuildingError::SqlSerializeError)
    }

    fn get_select_clause(&self) -> String {
        self.columns.join(", ")
    }

    fn get_group_by_clause(&self) -> String {
        self.group_by.join(", ")
    }

    fn get_outer_select_clause(&self) -> String {
        self.outer_select.join(", ")
    }

    pub fn add_having_clause<R>(
        &mut self,
        aggregate: Aggregate<R>,
        filter_type: FilterTypes,
        value: impl ToSql<T>,
    ) -> QueryResult<()>
    where
        Aggregate<R>: ToSql<T>,
    {
        let aggregate = aggregate
            .to_sql(&self.table_engine)
            .change_context(QueryBuildingError::SqlSerializeError)
            .attach_printable("Error serializing having aggregate")?;
        let value = value
            .to_sql(&self.table_engine)
            .change_context(QueryBuildingError::SqlSerializeError)
            .attach_printable("Error serializing having value")?;
        let entry = (aggregate, filter_type, value);
        if let Some(having) = &mut self.having {
            having.push(entry);
        } else {
            self.having = Some(vec![entry]);
        }
        Ok(())
    }

    pub fn add_outer_select_column(&mut self, column: impl ToSql<T>) -> QueryResult<()> {
        self.outer_select.push(
            column
                .to_sql(&self.table_engine)
                .change_context(QueryBuildingError::SqlSerializeError)
                .attach_printable("Error serializing outer select column")?,
        );
        Ok(())
    }

    pub fn get_filter_type_clause(&self) -> Option<String> {
        self.having.as_ref().map(|vec| {
            vec.iter()
                .map(|(l, op, r)| filter_type_to_sql(l, *op, r))
                .collect::<Vec<String>>()
                .join(" AND ")
        })
    }

    pub fn build_query(&mut self) -> QueryResult<String>
    where
        Aggregate<&'static str>: ToSql<T>,
        Window<&'static str>: ToSql<T>,
    {
        if self.columns.is_empty() {
            Err(QueryBuildingError::InvalidQuery(
                "No select fields provided",
            ))?;
        }
        let mut query = String::from("SELECT ");

        if self.distinct {
            query.push_str("DISTINCT ");
        }

        query.push_str(&self.get_select_clause());

        query.push_str(" FROM ");

        query.push_str(
            &self
                .table
                .to_sql(&self.table_engine)
                .change_context(QueryBuildingError::SqlSerializeError)
                .attach_printable("Error serializing table value")?,
        );

        let filter_clause = self.get_filter_clause()?;
        if !filter_clause.is_empty() {
            query.push_str(" WHERE ");
            query.push_str(filter_clause.as_str());
        }

        if !self.group_by.is_empty() {
            query.push_str(" GROUP BY ");
            query.push_str(&self.get_group_by_clause());
            if let TableEngine::CollapsingMergeTree { sign } = self.table_engine {
                self.add_having_clause(
                    Aggregate::Count {
                        field: Some(sign),
                        alias: None,
                    },
                    FilterTypes::Gte,
                    "1",
                )?;
            }
        }

        if self.having.is_some() {
            if let Some(condition) = self.get_filter_type_clause() {
                query.push_str(" HAVING ");
                query.push_str(condition.as_str());
            }
        }

        if !self.order_by.is_empty() {
            query.push_str(" ORDER BY ");
            query.push_str(&self.order_by.join(", "));
        }

        if let Some(limit_by) = &self.limit_by {
            query.push_str(&format!(" {limit_by}"));
        }

        if !self.outer_select.is_empty() {
            query.insert_str(
                0,
                format!("SELECT {} FROM (", &self.get_outer_select_clause()).as_str(),
            );
            query.push_str(") _");
        }

        if let Some(top_n) = &self.top_n {
            query.insert_str(0, "SELECT * FROM (");
            query.push_str(format!(") _ WHERE top_n <= {}", top_n.count).as_str());
        }

        logger::debug!(%query);

        Ok(query)
    }

    pub async fn execute_query<R, P>(
        &mut self,
        store: &P,
    ) -> CustomResult<CustomResult<Vec<R>, QueryExecutionError>, QueryBuildingError>
    where
        P: LoadRow<R> + AnalyticsDataSource,
        Aggregate<&'static str>: ToSql<T>,
        Window<&'static str>: ToSql<T>,
    {
        let query = self
            .build_query()
            .change_context(QueryBuildingError::SqlSerializeError)
            .attach_printable("Failed to execute query")?;

        Ok(store.load_results(query.as_str()).await)
    }
}

impl<T> QueryFilter<T> for AuthInfo
where
    T: AnalyticsDataSource,
    AnalyticsCollection: ToSql<T>,
{
    fn set_filter_clause(&self, builder: &mut QueryBuilder<T>) -> QueryResult<()> {
        match self {
            Self::OrgLevel { org_id } => {
                builder
                    .add_filter_clause("organization_id", org_id)
                    .attach_printable("Error adding organization_id filter")?;
            }
            Self::MerchantLevel {
                org_id,
                merchant_ids,
            } => {
                builder
                    .add_filter_clause("organization_id", org_id)
                    .attach_printable("Error adding organization_id filter")?;
                builder
                    .add_filter_in_range_clause("merchant_id", merchant_ids)
                    .attach_printable("Error adding merchant_id filter")?;
            }
            Self::ProfileLevel {
                org_id,
                merchant_id,
                profile_ids,
            } => {
                builder
                    .add_filter_clause("organization_id", org_id)
                    .attach_printable("Error adding organization_id filter")?;
                builder
                    .add_filter_clause("merchant_id", merchant_id)
                    .attach_printable("Error adding merchant_id filter")?;
                builder
                    .add_filter_in_range_clause("profile_id", profile_ids)
                    .attach_printable("Error adding profile_id filter")?;
            }
        }
        Ok(())
    }
}


use common_enums::{enums, AuthenticationConnectors};
#[cfg(feature = "v1")]
use common_utils::errors::{self, CustomResult};
use common_utils::{
    events::{ApiEventMetric, ApiEventsType},
    id_type,
};
#[cfg(feature = "v1")]
use error_stack::ResultExt;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use utoipa::ToSchema;

#[cfg(feature = "v1")]
use crate::payments::{Address, BrowserInformation, PaymentMethodData};
use crate::payments::{
    ClickToPaySessionResponse, CustomerDetails, DeviceChannel, SdkInformation,
    ThreeDsCompletionIndicator,
};

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationCreateRequest {
    /// The unique identifier for this authentication.
    #[schema(value_type = Option<String>, example = "auth_mbabizu24mvu3mela5njyhpit4")]
    pub authentication_id: Option<id_type::AuthenticationId>,

    /// The business profile that is associated with this authentication
    #[schema(value_type = Option<String>)]
    pub profile_id: Option<id_type::ProfileId>,

    /// Customer details.
    #[schema(value_type = Option<CustomerDetails>)]
    pub customer: Option<CustomerDetails>,

    /// The amount for the transaction, required.
    #[schema(value_type = MinorUnit, example = 1000)]
    pub amount: common_utils::types::MinorUnit,

    /// The connector to be used for authentication, if known.
    #[schema(value_type = Option<AuthenticationConnectors>, example = "netcetera")]
    pub authentication_connector: Option<AuthenticationConnectors>,

    /// The currency for the transaction, required.
    #[schema(value_type = Currency)]
    pub currency: common_enums::Currency,

    /// The URL to which the user should be redirected after authentication.
    #[schema(value_type = Option<String>, example = "https://example.com/redirect")]
    pub return_url: Option<String>,

    /// Force 3DS challenge.
    #[serde(default)]
    pub force_3ds_challenge: Option<bool>,

    /// Choose what kind of sca exemption is required for this payment
    #[schema(value_type = Option<ScaExemptionType>)]
    pub psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,

    /// Profile Acquirer ID get from profile acquirer configuration
    #[schema(value_type = Option<String>)]
    pub profile_acquirer_id: Option<id_type::ProfileAcquirerId>,

    /// Acquirer details information
    #[schema(value_type = Option<AcquirerDetails>)]
    pub acquirer_details: Option<AcquirerDetails>,

    /// Customer details.
    #[schema(value_type = Option<CustomerDetails>)]
    pub customer_details: Option<CustomerDetails>,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AcquirerDetails {
    /// The bin of the card.
    #[schema(value_type = Option<String>, example = "123456")]
    pub acquirer_bin: Option<String>,
    /// The merchant id of the card.
    #[schema(value_type = Option<String>, example = "merchant_abc")]
    pub acquirer_merchant_id: Option<String>,
    /// The country code of the card.
    #[schema(value_type = Option<String>, example = "US/34456")]
    pub merchant_country_code: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationResponse {
    /// The unique identifier for this authentication.
    #[schema(value_type = String, example = "auth_mbabizu24mvu3mela5njyhpit4")]
    pub authentication_id: id_type::AuthenticationId,

    /// This is an identifier for the merchant account. This is inferred from the API key
    /// provided during the request
    #[schema(value_type = String, example = "merchant_abc")]
    pub merchant_id: id_type::MerchantId,

    /// The current status of the authentication (e.g., Started).
    #[schema(value_type = AuthenticationStatus)]
    pub status: common_enums::AuthenticationStatus,

    /// The client secret for this authentication, to be used for client-side operations.
    #[schema(value_type = Option<String>, example = "auth_mbabizu24mvu3mela5njyhpit4_secret_el9ksDkiB8hi6j9N78yo")]
    pub client_secret: Option<masking::Secret<String>>,

    /// The amount for the transaction.
    #[schema(value_type = MinorUnit, example = 1000)]
    pub amount: common_utils::types::MinorUnit,

    /// The currency for the transaction.
    #[schema(value_type = Currency)]
    pub currency: enums::Currency,

    /// The connector to be used for authentication, if known.
    #[schema(value_type = Option<AuthenticationConnectors>, example = "netcetera")]
    pub authentication_connector: Option<AuthenticationConnectors>,

    /// Whether 3DS challenge was forced.
    pub force_3ds_challenge: Option<bool>,

    /// The URL to which the user should be redirected after authentication, if provided.
    pub return_url: Option<String>,

    #[schema(example = "2022-09-10T10:11:12Z")]
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub created_at: Option<PrimitiveDateTime>,

    #[schema(example = "E0001")]
    pub error_code: Option<String>,

    /// If there was an error while calling the connector the error message is received here
    #[schema(example = "Failed while verifying the card")]
    pub error_message: Option<String>,

    /// The business profile that is associated with this payment
    #[schema(value_type = Option<String>)]
    pub profile_id: Option<id_type::ProfileId>,

    /// Choose what kind of sca exemption is required for this payment
    #[schema(value_type = Option<ScaExemptionType>)]
    pub psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,

    /// Acquirer details information
    #[schema(value_type = Option<AcquirerDetails>)]
    pub acquirer_details: Option<AcquirerDetails>,

    /// Profile Acquirer ID get from profile acquirer configuration
    #[schema(value_type = Option<String>)]
    pub profile_acquirer_id: Option<id_type::ProfileAcquirerId>,

    /// Customer details.
    #[schema(value_type = Option<CustomerDetails>)]
    pub customer_details: Option<CustomerDetails>,
}

impl ApiEventMetric for AuthenticationCreateRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        self.authentication_id
            .as_ref()
            .map(|id| ApiEventsType::Authentication {
                authentication_id: id.clone(),
            })
    }
}

impl ApiEventMetric for AuthenticationResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[cfg(feature = "v1")]
impl ApiEventMetric for AuthenticationEligibilityCheckRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[cfg(feature = "v1")]
impl ApiEventMetric for AuthenticationEligibilityCheckResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[cfg(feature = "v1")]
impl ApiEventMetric for AuthenticationRetrieveEligibilityCheckRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[cfg(feature = "v1")]
impl ApiEventMetric for AuthenticationRetrieveEligibilityCheckResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationEligibilityRequest {
    /// Payment method-specific data such as card details, wallet info, etc.
    /// This holds the raw information required to process the payment method.
    #[schema(value_type = PaymentMethodData)]
    pub payment_method_data: PaymentMethodData,

    /// Enum representing the type of payment method being used
    /// (e.g., Card, Wallet, UPI, BankTransfer, etc.).
    #[schema(value_type = PaymentMethod)]
    pub payment_method: common_enums::PaymentMethod,

    /// Can be used to specify the Payment Method Type
    #[schema(value_type = Option<PaymentMethodType>, example = "debit")]
    pub payment_method_type: Option<enums::PaymentMethodType>,

    /// Optional secret value used to identify and authorize the client making the request.
    /// This can help ensure that the payment session is secure and valid.
    #[schema(value_type = Option<String>)]
    pub client_secret: Option<masking::Secret<String>>,

    /// Optional identifier for the business profile associated with the payment.
    /// This determines which configurations, rules, and branding are applied to the transaction.
    #[schema(value_type = Option<String>)]
    pub profile_id: Option<id_type::ProfileId>,

    /// Optional billing address of the customer.
    /// This can be used for fraud detection, authentication, or compliance purposes.
    #[schema(value_type = Option<Address>)]
    pub billing: Option<Address>,

    /// Optional shipping address of the customer.
    /// This can be useful for logistics, verification, or additional risk checks.
    #[schema(value_type = Option<Address>)]
    pub shipping: Option<Address>,

    /// Optional information about the customer's browser (user-agent, language, etc.).
    /// This is typically used to support 3DS authentication flows and improve risk assessment.
    #[schema(value_type = Option<BrowserInformation>)]
    pub browser_information: Option<BrowserInformation>,

    /// Optional email address of the customer.
    /// Used for customer identification, communication, and possibly for 3DS or fraud checks.
    #[schema(value_type = Option<String>)]
    pub email: Option<common_utils::pii::Email>,
}

#[cfg(feature = "v1")]
impl AuthenticationEligibilityRequest {
    pub fn get_next_action_api(
        &self,
        base_url: String,
        authentication_id: String,
    ) -> CustomResult<NextAction, errors::ParsingError> {
        let url = format!("{base_url}/authentication/{authentication_id}/authenticate");
        Ok(NextAction {
            url: url::Url::parse(&url).change_context(errors::ParsingError::UrlParsingError)?,
            http_method: common_utils::request::Method::Post,
        })
    }

    pub fn get_billing_address(&self) -> Option<Address> {
        self.billing.clone()
    }

    pub fn get_shipping_address(&self) -> Option<Address> {
        self.shipping.clone()
    }

    pub fn get_browser_information(&self) -> Option<BrowserInformation> {
        self.browser_information.clone()
    }
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, ToSchema)]
pub struct AuthenticationEligibilityResponse {
    /// The unique identifier for this authentication.
    #[schema(value_type = String, example = "auth_mbabizu24mvu3mela5njyhpit4")]
    pub authentication_id: id_type::AuthenticationId,
    /// The URL to which the user should be redirected after authentication.
    #[schema(value_type = NextAction)]
    pub next_action: NextAction,
    /// The current status of the authentication (e.g., Started).
    #[schema(value_type = AuthenticationStatus)]
    pub status: common_enums::AuthenticationStatus,
    /// The 3DS data for this authentication.
    #[schema(value_type = Option<EligibilityResponseParams>)]
    pub eligibility_response_params: Option<EligibilityResponseParams>,
    /// The metadata for this authentication.
    #[schema(value_type = serde_json::Value)]
    pub connector_metadata: Option<serde_json::Value>,
    /// The unique identifier for this authentication.
    #[schema(value_type = String)]
    pub profile_id: id_type::ProfileId,
    /// The error message for this authentication.
    #[schema(value_type = Option<String>)]
    pub error_message: Option<String>,
    /// The error code for this authentication.
    #[schema(value_type = Option<String>)]
    pub error_code: Option<String>,
    /// The connector used for this authentication.
    #[schema(value_type = Option<AuthenticationConnectors>)]
    pub authentication_connector: Option<AuthenticationConnectors>,
    /// Billing address
    #[schema(value_type = Option<Address>)]
    pub billing: Option<Address>,
    /// Shipping address
    #[schema(value_type = Option<Address>)]
    pub shipping: Option<Address>,
    /// Browser information
    #[schema(value_type = Option<BrowserInformation>)]
    pub browser_information: Option<BrowserInformation>,
    /// Email
    #[schema(value_type = Option<String>)]
    pub email: common_utils::crypto::OptionalEncryptableEmail,
    /// Acquirer details information.
    #[schema(value_type = Option<AcquirerDetails>)]
    pub acquirer_details: Option<AcquirerDetails>,
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationEligibilityCheckRequest {
    /// The unique identifier for this authentication.
    /// Added in the request for api event metrics, populated from path parameter
    #[serde(skip)]
    pub authentication_id: id_type::AuthenticationId,
    /// Optional secret value used to identify and authorize the client making the request.
    /// This can help ensure that the payment session is secure and valid.
    #[schema(value_type = Option<String>)]
    pub client_secret: Option<masking::Secret<String>>,
    /// The data for this authentication eligibility check.
    pub eligibility_check_data: AuthenticationEligibilityCheckData,
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, ToSchema)]
pub struct AuthenticationEligibilityCheckResponse {
    /// The unique identifier for this authentication.
    #[schema(value_type = String, example = "auth_mbabizu24mvu3mela5njyhpit4")]
    pub authentication_id: id_type::AuthenticationId,
    // The next action for this authentication eligibility check.
    pub sdk_next_action: AuthenticationSdkNextAction,
}

#[derive(Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Clone, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum AuthenticationSdkNextAction {
    /// The next action is to await for a merchant callback
    AwaitMerchantCallback,
    /// The next action is to deny the payment with an error message
    Deny { message: String },
    /// The next action is to proceed with the payment
    Proceed,
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationRetrieveEligibilityCheckRequest {
    /// The unique identifier for this authentication.
    /// Added in the request for api event metrics, populated from path parameter
    #[serde(skip)]
    pub authentication_id: id_type::AuthenticationId,
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationRetrieveEligibilityCheckResponse {
    /// The unique identifier for this authentication.
    /// Added in the request for api event metrics, populated from path parameter
    #[serde(skip)]
    pub authentication_id: id_type::AuthenticationId,
    /// The data for this authentication eligibility check.
    pub eligibility_check_data: AuthenticationEligibilityCheckResponseData,
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum AuthenticationEligibilityCheckData {
    ClickToPay(ClickToPayEligibilityCheckData),
}

#[cfg(feature = "v1")]
impl AuthenticationEligibilityCheckData {
    pub fn get_click_to_pay_data(&self) -> Option<&ClickToPayEligibilityCheckData> {
        match self {
            Self::ClickToPay(data) => Some(data),
        }
    }
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum AuthenticationEligibilityCheckResponseData {
    ClickToPayEnrollmentStatus(ClickToPayEligibilityCheckResponseData),
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct ClickToPayEligibilityCheckData {
    // Visa specific eligibility check data
    pub visa: Option<VisaEligibilityCheckData>,
    // MasterCard specific eligibility check data
    pub mastercard: Option<MasterCardEligibilityCheckData>,
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct ClickToPayEligibilityCheckResponseData {
    // Visa specific eligibility check data
    pub visa: Option<bool>,
    // MasterCard specific eligibility check data
    pub mastercard: Option<bool>,
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct VisaEligibilityCheckData {
    // Indicates whether the consumer is enrolled in Visa Secure program
    pub consumer_present: bool,
    // Status of the consumer in Visa Secure program]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub consumer_status: Option<String>,
    // Additional data for eligibility check
    #[serde(skip_serializing_if = "Option::is_none")]
    #[schema(value_type = Option<BrowserInformation>)]
    pub custom_data: Option<common_utils::pii::SecretSerdeValue>,
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MasterCardEligibilityCheckData {
    // Indicates whether the consumer is enrolled in MasterCard Identity Check program
    pub consumer_present: bool,
    // Session ID from MasterCard Identity Check program
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id_lookup_session_id: Option<String>,
    // Timestamp of the last time the card was used
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_used_card_timestamp: Option<String>,
}

#[derive(Debug, Serialize, ToSchema)]
pub enum EligibilityResponseParams {
    ThreeDsData(ThreeDsData),
}

#[derive(Debug, Serialize, ToSchema)]
pub struct ThreeDsData {
    /// The unique identifier for this authentication from the 3DS server.
    #[schema(value_type = String)]
    pub three_ds_server_transaction_id: Option<String>,
    /// The maximum supported 3DS version.
    #[schema(value_type = String)]
    pub maximum_supported_3ds_version: Option<common_utils::types::SemanticVersion>,
    /// The unique identifier for this authentication from the connector.
    #[schema(value_type = String)]
    pub connector_authentication_id: Option<String>,
    /// The data required to perform the 3DS method.
    #[schema(value_type = String)]
    pub three_ds_method_data: Option<String>,
    /// The URL to which the user should be redirected after authentication.
    #[schema(value_type = String, example = "https://example.com/redirect")]
    pub three_ds_method_url: Option<url::Url>,
    /// The version of the message.
    #[schema(value_type = String)]
    pub message_version: Option<common_utils::types::SemanticVersion>,
    /// The unique identifier for this authentication.
    #[schema(value_type = String)]
    pub directory_server_id: Option<String>,
}

#[derive(Debug, Serialize, ToSchema)]
pub struct NextAction {
    /// The URL for authenticatating the user.
    #[schema(value_type = String)]
    pub url: url::Url,
    /// The HTTP method to use for the request.
    #[schema(value_type = Method)]
    pub http_method: common_utils::request::Method,
}

#[cfg(feature = "v1")]
impl ApiEventMetric for AuthenticationEligibilityRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        None
    }
}

#[cfg(feature = "v1")]
impl ApiEventMetric for AuthenticationEligibilityResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationAuthenticateRequest {
    /// Authentication ID for the authentication
    #[serde(skip_deserializing)]
    pub authentication_id: id_type::AuthenticationId,
    /// Client secret for the authentication
    #[schema(value_type = String)]
    pub client_secret: Option<masking::Secret<String>>,
    /// SDK Information if request is from SDK
    pub sdk_information: Option<SdkInformation>,
    /// Device Channel indicating whether request is coming from App or Browser
    pub device_channel: DeviceChannel,
    /// Indicates if 3DS method data was successfully completed or not
    pub threeds_method_comp_ind: ThreeDsCompletionIndicator,
}

impl ApiEventMetric for AuthenticationAuthenticateRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationAuthenticateResponse {
    /// Indicates the transaction status
    #[serde(rename = "trans_status")]
    #[schema(value_type = Option<TransactionStatus>)]
    pub transaction_status: Option<common_enums::TransactionStatus>,
    /// Access Server URL to be used for challenge submission
    #[schema(value_type = String, example = "https://example.com/redirect")]
    pub acs_url: Option<url::Url>,
    /// Challenge request which should be sent to acs_url
    pub challenge_request: Option<String>,
    /// Unique identifier assigned by the EMVCo(Europay, Mastercard and Visa)
    pub acs_reference_number: Option<String>,
    /// Unique identifier assigned by the ACS to identify a single transaction
    pub acs_trans_id: Option<String>,
    /// Unique identifier assigned by the 3DS Server to identify a single transaction
    pub three_ds_server_transaction_id: Option<String>,
    /// Contains the JWS object created by the ACS for the ARes(Authentication Response) message
    pub acs_signed_content: Option<String>,
    /// Three DS Requestor URL
    pub three_ds_requestor_url: String,
    /// Merchant app declaring their URL within the CReq message so that the Authentication app can call the Merchant app after OOB authentication has occurred
    pub three_ds_requestor_app_url: Option<String>,

    /// The error message for this authentication.
    #[schema(value_type = String)]
    pub error_message: Option<String>,
    /// The error code for this authentication.
    #[schema(value_type = String)]
    pub error_code: Option<String>,
    /// The authentication value for this authentication, only available in case of server to server request. Unavailable in case of client request due to security concern.
    #[schema(value_type = String)]
    pub authentication_value: Option<masking::Secret<String>>,

    /// The current status of the authentication (e.g., Started).
    #[schema(value_type = AuthenticationStatus)]
    pub status: common_enums::AuthenticationStatus,

    /// The connector to be used for authentication, if known.
    #[schema(value_type = Option<AuthenticationConnectors>, example = "netcetera")]
    pub authentication_connector: Option<AuthenticationConnectors>,

    /// The unique identifier for this authentication.
    #[schema(value_type = AuthenticationId, example = "auth_mbabizu24mvu3mela5njyhpit4")]
    pub authentication_id: id_type::AuthenticationId,

    /// The ECI value for this authentication.
    #[schema(value_type = String)]
    pub eci: Option<String>,

    /// Acquirer details information.
    #[schema(value_type = Option<AcquirerDetails>)]
    pub acquirer_details: Option<AcquirerDetails>,
}

impl ApiEventMetric for AuthenticationAuthenticateResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[cfg(feature = "v1")]
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct AuthenticationSyncResponse {
    // Core Authentication Fields (from AuthenticationResponse)
    /// The unique identifier for this authentication.
    #[schema(value_type = String, example = "auth_mbabizu24mvu3mela5njyhpit4")]
    pub authentication_id: id_type::AuthenticationId,

    /// This is an identifier for the merchant account.
    #[schema(value_type = String, example = "merchant_abc")]
    pub merchant_id: id_type::MerchantId,

    /// The current status of the authentication.
    #[schema(value_type = AuthenticationStatus)]
    pub status: common_enums::AuthenticationStatus,

    /// The client secret for this authentication.
    #[schema(value_type = Option<String>)]
    pub client_secret: Option<masking::Secret<String>>,

    /// The amount for the transaction.
    #[schema(value_type = MinorUnit, example = 1000)]
    pub amount: common_utils::types::MinorUnit,

    /// The currency for the transaction.
    #[schema(value_type = Currency)]
    pub currency: enums::Currency,

    /// The connector used for authentication.
    #[schema(value_type = Option<AuthenticationConnectors>)]
    pub authentication_connector: Option<AuthenticationConnectors>,

    /// Whether 3DS challenge was forced.
    pub force_3ds_challenge: Option<bool>,

    /// The URL to which the user should be redirected after authentication.
    pub return_url: Option<String>,

    #[schema(example = "2022-09-10T10:11:12Z")]
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,

    /// The business profile that is associated with this authentication.
    #[schema(value_type = String)]
    pub profile_id: id_type::ProfileId,

    /// SCA exemption type for this authentication.
    #[schema(value_type = Option<ScaExemptionType>)]
    pub psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,

    /// Acquirer details information.
    #[schema(value_type = Option<AcquirerDetails>)]
    pub acquirer_details: Option<AcquirerDetails>,

    /// The unique identifier from the 3DS server.
    #[schema(value_type = Option<String>)]
    pub threeds_server_transaction_id: Option<String>,

    /// The maximum supported 3DS version.
    #[schema(value_type = Option<String>)]
    pub maximum_supported_3ds_version: Option<common_utils::types::SemanticVersion>,

    /// The unique identifier from the connector.
    #[schema(value_type = Option<String>)]
    pub connector_authentication_id: Option<String>,

    /// The data required to perform the 3DS method.
    #[schema(value_type = Option<String>)]
    pub three_ds_method_data: Option<String>,

    /// The URL for the 3DS method.
    #[schema(value_type = Option<String>)]
    pub three_ds_method_url: Option<String>,

    /// The version of the message.
    #[schema(value_type = Option<String>)]
    pub message_version: Option<common_utils::types::SemanticVersion>,

    /// The metadata for this authentication.
    #[schema(value_type = Option<serde_json::Value>)]
    pub connector_metadata: Option<serde_json::Value>,

    /// The unique identifier for the directory server.
    #[schema(value_type = Option<String>)]
    pub directory_server_id: Option<String>,

    /// The insensitive payment method data
    pub payment_method_data: Option<AuthenticationPaymentMethodDataResponse>,

    /// The tokens for vaulted data
    pub vault_token_data: Option<AuthenticationVaultTokenData>,

    /// Billing address.
    #[schema(value_type = Option<Address>)]
    pub billing: Option<Address>,

    /// Shipping address.
    #[schema(value_type = Option<Address>)]
    pub shipping: Option<Address>,

    /// Browser information.
    #[schema(value_type = Option<BrowserInformation>)]
    pub browser_information: Option<BrowserInformation>,

    /// Email.
    #[schema(value_type = Option<String>)]
    pub email: common_utils::crypto::OptionalEncryptableEmail,

    /// Indicates the transaction status.
    #[serde(rename = "trans_status")]
    #[schema(value_type = Option<TransactionStatus>)]
    pub transaction_status: Option<common_enums::TransactionStatus>,

    /// Access Server URL for challenge submission.
    pub acs_url: Option<String>,

    /// Challenge request to be sent to acs_url.
    pub challenge_request: Option<String>,

    /// Unique identifier assigned by EMVCo.
    pub acs_reference_number: Option<String>,

    /// Unique identifier assigned by the ACS.
    pub acs_trans_id: Option<String>,

    /// JWS object created by the ACS for the ARes message.
    pub acs_signed_content: Option<String>,

    /// Three DS Requestor URL.
    pub three_ds_requestor_url: Option<String>,

    /// Merchant app URL for OOB authentication.
    pub three_ds_requestor_app_url: Option<String>,

    /// ECI value for this authentication, only available in case of server to server request. Unavailable in case of client request due to security concern.
    pub eci: Option<String>,

    // Common Error Fields (present in multiple responses)
    /// Error message if any.
    #[schema(value_type = Option<String>)]
    pub error_message: Option<String>,

    /// Error code if any.
    #[schema(value_type = Option<String>)]
    pub error_code: Option<String>,

    /// Profile Acquirer ID
    #[schema(value_type = Option<String>)]
    pub profile_acquirer_id: Option<id_type::ProfileAcquirerId>,
}

#[cfg(feature = "v1")]
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AuthenticationPaymentMethodDataResponse {
    CardData {
        /// card expiry year
        #[schema(value_type = Option<String>)]
        card_expiry_year: Option<masking::Secret<String>>,

        /// card expiry month
        #[schema(value_type = Option<String>)]
        card_expiry_month: Option<masking::Secret<String>>,
    },
    NetworkTokenData {
        /// network token expiry month
        #[schema(value_type = Option<String>)]
        network_token_expiry_month: Option<masking::Secret<String>>,

        /// network token expiry year
        #[schema(value_type = Option<String>)]
        network_token_expiry_year: Option<masking::Secret<String>>,
    },
}

#[cfg(feature = "v1")]
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AuthenticationVaultTokenData {
    CardData {
        /// token representing card_number
        #[schema(value_type = Option<String>)]
        #[serde(rename = "card_number")]
        tokenized_card_number: Option<masking::Secret<String>>,

        /// token representing card_expiry_year
        #[schema(value_type = Option<String>)]
        #[serde(rename = "card_expiry_year")]
        tokenized_card_expiry_year: Option<masking::Secret<String>>,

        /// token representing card_expiry_month
        #[schema(value_type = Option<String>)]
        #[serde(rename = "card_expiry_month")]
        tokenized_card_expiry_month: Option<masking::Secret<String>>,

        /// token representing card_cvc
        #[schema(value_type = Option<String>)]
        #[serde(rename = "card_cvc")]
        tokenized_card_cvc: Option<masking::Secret<String>>,
    },
    NetworkTokenData {
        /// token representing payment_token
        #[schema(value_type = Option<String>)]
        #[serde(rename = "network_token")]
        tokenized_network_token: Option<masking::Secret<String>>,

        /// token representing token_expiry_year
        #[schema(value_type = Option<String>)]
        #[serde(rename = "network_token_expiry_year")]
        tokenized_expiry_year: Option<masking::Secret<String>>,

        /// token representing token_expiry_month
        #[schema(value_type = Option<String>)]
        #[serde(rename = "network_token_expiry_month")]
        tokenized_expiry_month: Option<masking::Secret<String>>,

        /// token representing token_cryptogram
        #[schema(value_type = Option<String>)]
        #[serde(rename = "network_token_cryptogram")]
        tokenized_cryptogram: Option<masking::Secret<String>>,
    },
}

#[cfg(feature = "v1")]
impl ApiEventMetric for AuthenticationSyncResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationSyncRequest {
    /// The client secret for this authentication.
    #[schema(value_type = String)]
    pub client_secret: Option<masking::Secret<String>>,
    /// Payment method data for Post Authentication sync
    pub payment_method_details: Option<PostAuthenticationRequestPaymentMethodData>,
    /// Authentication ID for the authentication
    #[serde(skip_deserializing)]
    pub authentication_id: id_type::AuthenticationId,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct PostAuthenticationRequestPaymentMethodData {
    pub payment_method_type: AuthenticationPaymentMethodType,
    pub payment_method_data: AuthenticationPaymentMethodData,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub enum AuthenticationPaymentMethodType {
    #[serde(rename = "ctp")]
    ClickToPay,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(untagged)]
pub enum AuthenticationPaymentMethodData {
    ClickToPayDetails(ClickToPayDetails),
}

impl AuthenticationPaymentMethodData {
    pub fn get_click_to_pay_details(&self) -> Option<&ClickToPayDetails> {
        match self {
            Self::ClickToPayDetails(details) => Some(details),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ClickToPayDetails {
    /// merchant transaction id
    pub merchant_transaction_id: Option<String>,
    /// network transaction correlation id
    pub correlation_id: Option<String>,
    /// session transaction flow id
    pub x_src_flow_id: Option<String>,
    /// provider Eg: Visa, Mastercard
    #[schema(value_type = Option<CtpServiceProvider>)]
    pub provider: Option<super::enums::CtpServiceProvider>,
    /// Encrypted payload
    #[schema(value_type = Option<String>)]
    pub encrypted_payload: Option<masking::Secret<String>>,
}

impl ApiEventMetric for AuthenticationSyncRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationSyncPostUpdateRequest {
    /// Authentication ID for the authentication
    #[serde(skip_deserializing)]
    pub authentication_id: id_type::AuthenticationId,
}

impl ApiEventMetric for AuthenticationSyncPostUpdateRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
pub struct AuthenticationSessionTokenRequest {
    /// Authentication ID for the authentication
    #[serde(skip_deserializing)]
    pub authentication_id: id_type::AuthenticationId,
    /// Client Secret for the authentication
    #[schema(value_type = String)]
    pub client_secret: Option<masking::Secret<String>>,
}

#[derive(Debug, serde::Serialize, Clone, ToSchema)]
pub struct AuthenticationSessionResponse {
    /// The identifier for the payment
    #[schema(value_type = String)]
    pub authentication_id: id_type::AuthenticationId,
    /// The list of session token object
    pub session_token: Vec<AuthenticationSessionToken>,
}

#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
#[serde(tag = "wallet_name")]
#[serde(rename_all = "snake_case")]
pub enum AuthenticationSessionToken {
    /// The sessions response structure for ClickToPay
    ClickToPay(Box<ClickToPaySessionResponse>),
    NoSessionTokenReceived,
}

impl ApiEventMetric for AuthenticationSessionTokenRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

impl ApiEventMetric for AuthenticationSessionResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}


//! Contains new types with restrictions
use masking::{ExposeInterface, PeekInterface, Secret};

use crate::{
    consts::MAX_ALLOWED_MERCHANT_NAME_LENGTH,
    pii::{Email, UpiVpaMaskingStrategy},
    transformers::ForeignFrom,
};

#[nutype::nutype(
    derive(Clone, Serialize, Deserialize, Debug),
    validate(len_char_min = 1, len_char_max = MAX_ALLOWED_MERCHANT_NAME_LENGTH)
)]
pub struct MerchantName(String);

impl masking::SerializableSecret for MerchantName {}

/// Function for masking alphanumeric characters in a string.
///
/// # Arguments
///     `val`
///         - holds reference to the string to be masked.
///     `unmasked_char_count`
///         - minimum character count to remain unmasked for identification
///         - this number is for keeping the characters unmasked from
///             both beginning (if feasible) and ending of the string.
///     `min_masked_char_count`
///         - this ensures the minimum number of characters to be masked
///
/// # Behaviour
///     - Returns the original string if its length is less than or equal to `unmasked_char_count`.
///     - If the string length allows, keeps `unmasked_char_count` characters unmasked at both start and end.
///     - Otherwise, keeps `unmasked_char_count` characters unmasked only at the end.
///     - Only alphanumeric characters are masked; other characters remain unchanged.
///
/// # Examples
///     Sort Code
///         (12-34-56, 2, 2) -> 12-**-56
///     Routing number
///         (026009593, 3, 3) -> 026***593
///     CNPJ
///         (12345678901, 4, 4) -> *******8901
///     CNPJ
///         (12345678901, 4, 3) -> 1234***8901
///     Pix key
///         (123e-a452-1243-1244-000, 4, 4) -> 123e-****-****-****-000
///     IBAN
///         (AL35202111090000000001234567, 5, 5) -> AL352******************34567
fn apply_mask(val: &str, unmasked_char_count: usize, min_masked_char_count: usize) -> String {
    let len = val.len();
    if len <= unmasked_char_count {
        return val.to_string();
    }

    let mask_start_index =
    // For showing only last `unmasked_char_count` characters
    if len < (unmasked_char_count * 2 + min_masked_char_count) {
        0
    // For showing first and last `unmasked_char_count` characters
    } else {
        unmasked_char_count
    };
    let mask_end_index = len - unmasked_char_count - 1;
    let range = mask_start_index..=mask_end_index;

    val.chars()
        .enumerate()
        .fold(String::new(), |mut acc, (index, ch)| {
            if ch.is_alphanumeric() && range.contains(&index) {
                acc.push('*');
            } else {
                acc.push(ch);
            }
            acc
        })
}

/// Masked sort code
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MaskedSortCode(Secret<String>);
impl From<String> for MaskedSortCode {
    fn from(src: String) -> Self {
        let masked_value = apply_mask(src.as_ref(), 2, 2);
        Self(Secret::from(masked_value))
    }
}
impl From<Secret<String>> for MaskedSortCode {
    fn from(secret: Secret<String>) -> Self {
        Self::from(secret.expose())
    }
}

/// Masked Routing number
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MaskedRoutingNumber(Secret<String>);
impl From<String> for MaskedRoutingNumber {
    fn from(src: String) -> Self {
        let masked_value = apply_mask(src.as_ref(), 3, 3);
        Self(Secret::from(masked_value))
    }
}
impl From<Secret<String>> for MaskedRoutingNumber {
    fn from(secret: Secret<String>) -> Self {
        Self::from(secret.expose())
    }
}

/// Masked bank account
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MaskedBankAccount(Secret<String>);
impl From<String> for MaskedBankAccount {
    fn from(src: String) -> Self {
        let masked_value = apply_mask(src.as_ref(), 4, 4);
        Self(Secret::from(masked_value))
    }
}

impl MaskedBankAccount {
    /// Expose the inner secret
    pub fn expose_inner(self) -> String {
        self.0.expose()
    }
}
impl From<Secret<String>> for MaskedBankAccount {
    fn from(secret: Secret<String>) -> Self {
        Self::from(secret.expose())
    }
}

/// Masked IBAN
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MaskedIban(Secret<String>);
impl From<String> for MaskedIban {
    fn from(src: String) -> Self {
        let masked_value = apply_mask(src.as_ref(), 5, 5);
        Self(Secret::from(masked_value))
    }
}
impl From<Secret<String>> for MaskedIban {
    fn from(secret: Secret<String>) -> Self {
        Self::from(secret.expose())
    }
}

/// Masked IBAN
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MaskedBic(Secret<String>);
impl From<String> for MaskedBic {
    fn from(src: String) -> Self {
        let masked_value = apply_mask(src.as_ref(), 3, 2);
        Self(Secret::from(masked_value))
    }
}
impl From<Secret<String>> for MaskedBic {
    fn from(secret: Secret<String>) -> Self {
        Self::from(secret.expose())
    }
}

/// Masked UPI ID
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MaskedUpiVpaId(Secret<String>);
impl From<String> for MaskedUpiVpaId {
    fn from(src: String) -> Self {
        let unmasked_char_count = 2;
        let masked_value = if let Some((user_identifier, bank_or_psp)) = src.split_once('@') {
            let masked_user_identifier = user_identifier
                .to_string()
                .chars()
                .take(unmasked_char_count)
                .collect::<String>()
                + &"*".repeat(user_identifier.len() - unmasked_char_count);
            format!("{masked_user_identifier}@{bank_or_psp}")
        } else {
            let masked_value = apply_mask(src.as_ref(), unmasked_char_count, 8);
            masked_value
        };

        Self(Secret::from(masked_value))
    }
}
impl From<Secret<String, UpiVpaMaskingStrategy>> for MaskedUpiVpaId {
    fn from(secret: Secret<String, UpiVpaMaskingStrategy>) -> Self {
        Self::from(secret.expose())
    }
}

/// Masked Email
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MaskedEmail(Secret<String>);
impl From<String> for MaskedEmail {
    fn from(src: String) -> Self {
        let unmasked_char_count = 2;
        let masked_value = if let Some((user_identifier, domain)) = src.split_once('@') {
            let masked_user_identifier = user_identifier
                .to_string()
                .chars()
                .take(unmasked_char_count)
                .collect::<String>()
                + &"*".repeat(user_identifier.len() - unmasked_char_count);
            format!("{masked_user_identifier}@{domain}")
        } else {
            let masked_value = apply_mask(src.as_ref(), unmasked_char_count, 8);
            masked_value
        };
        Self(Secret::from(masked_value))
    }
}
impl From<Secret<String>> for MaskedEmail {
    fn from(secret: Secret<String>) -> Self {
        Self::from(secret.expose())
    }
}
impl ForeignFrom<Email> for MaskedEmail {
    fn foreign_from(email: Email) -> Self {
        let email_value: String = email.expose().peek().to_owned();
        Self::from(email_value)
    }
}

/// Masked Phone Number
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MaskedPhoneNumber(Secret<String>);
impl From<String> for MaskedPhoneNumber {
    fn from(src: String) -> Self {
        let unmasked_char_count = 2;
        let masked_value = if unmasked_char_count <= src.len() {
            let len = src.len();
            // mask every character except the last 2
            "*".repeat(len - unmasked_char_count).to_string()
                + src
                    .get(len.saturating_sub(unmasked_char_count)..)
                    .unwrap_or("")
        } else {
            src
        };
        Self(Secret::from(masked_value))
    }
}
impl From<Secret<String>> for MaskedPhoneNumber {
    fn from(secret: Secret<String>) -> Self {
        Self::from(secret.expose())
    }
}

/// Masked Psp token
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MaskedPspToken(Secret<String>);
impl From<String> for MaskedPspToken {
    fn from(src: String) -> Self {
        let masked_value = apply_mask(src.as_ref(), 3, 3);
        Self(Secret::from(masked_value))
    }
}
impl From<Secret<String>> for MaskedPspToken {
    fn from(secret: Secret<String>) -> Self {
        Self::from(secret.expose())
    }
}

#[cfg(test)]
mod apply_mask_fn_test {
    use masking::PeekInterface;

    use crate::new_type::{
        apply_mask, MaskedBankAccount, MaskedIban, MaskedRoutingNumber, MaskedSortCode,
        MaskedUpiVpaId,
    };
    #[test]
    fn test_masked_types() {
        let sort_code = MaskedSortCode::from("110011".to_string());
        let routing_number = MaskedRoutingNumber::from("056008849".to_string());
        let bank_account = MaskedBankAccount::from("12345678901234".to_string());
        let iban = MaskedIban::from("NL02ABNA0123456789".to_string());
        let upi_vpa = MaskedUpiVpaId::from("someusername@okhdfcbank".to_string());

        // Standard masked data tests
        assert_eq!(sort_code.0.peek().to_owned(), "11**11".to_string());
        assert_eq!(routing_number.0.peek().to_owned(), "056***849".to_string());
        assert_eq!(
            bank_account.0.peek().to_owned(),
            "1234******1234".to_string()
        );
        assert_eq!(iban.0.peek().to_owned(), "NL02A********56789".to_string());
        assert_eq!(
            upi_vpa.0.peek().to_owned(),
            "so**********@okhdfcbank".to_string()
        );
    }

    #[test]
    fn test_apply_mask_fn() {
        let value = "12345678901".to_string();

        // Generic masked tests
        assert_eq!(apply_mask(&value, 2, 2), "12*******01".to_string());
        assert_eq!(apply_mask(&value, 3, 2), "123*****901".to_string());
        assert_eq!(apply_mask(&value, 3, 3), "123*****901".to_string());
        assert_eq!(apply_mask(&value, 4, 3), "1234***8901".to_string());
        assert_eq!(apply_mask(&value, 4, 4), "*******8901".to_string());
        assert_eq!(apply_mask(&value, 5, 4), "******78901".to_string());
        assert_eq!(apply_mask(&value, 5, 5), "******78901".to_string());
        assert_eq!(apply_mask(&value, 6, 5), "*****678901".to_string());
        assert_eq!(apply_mask(&value, 6, 6), "*****678901".to_string());
        assert_eq!(apply_mask(&value, 7, 6), "****5678901".to_string());
        assert_eq!(apply_mask(&value, 7, 7), "****5678901".to_string());
        assert_eq!(apply_mask(&value, 8, 7), "***45678901".to_string());
        assert_eq!(apply_mask(&value, 8, 8), "***45678901".to_string());
    }
}