use common_enums::{CountryAlpha2, TransactionStatus};
use common_utils::{pii, types::SemanticVersion, types::StringMajorUnit};
use domain_types::payment_method_data::{PaymentMethodDataTypes, RawCardNumber};
use hyperswitch_masking::Secret;
use serde::Serialize;

use crate::utils::MerchantDefinedInformation;
use cards;

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BarclaycardPaymentsRequest<T: PaymentMethodDataTypes + Sync + Send + 'static + Serialize>
{
    pub processing_information: ProcessingInformation,
    pub payment_information: PaymentInformation<T>,
    pub order_information: OrderInformationWithBill,
    pub client_reference_information: ClientReferenceInformation,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub consumer_authentication_information: Option<ConsumerAuthenticationInformation>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub merchant_defined_information: Option<Vec<MerchantDefinedInformation>>,
}

/// Full consumer-authentication-information block sent on the Authorize request.
///
/// Mirrors Cybersource's `CybersourceConsumerAuthInformation`. For non-3DS payments the
/// whole block is `None` on `BarclaycardPaymentsRequest`; for wallet payments (ApplePay)
/// only `ucaf_collection_indicator` is populated. When the Authorize request carries
/// external 3DS authentication results, the block is built via
/// `From<router_request_types::AuthenticationData>`. Every field is skip-if-none so the
/// wire shape for the existing non-3DS / wallet flows is unchanged.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ConsumerAuthenticationInformation {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cavv: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ucaf_collection_indicator: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ucaf_authentication_data: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub xid: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub directory_server_transaction_id: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub specification_version: Option<SemanticVersion>,
    /// This field specifies the 3ds version
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pa_specification_version: Option<SemanticVersion>,
    /// Raw electronic commerce indicator (ECI). This is a network status code, not PII.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub eci_raw: Option<String>,
    /// Payer authentication response status. Applicable for Mastercard and Visa.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pares_status: Option<BarclaycardParesStatus>,
    /// Transaction ID generated by the access control server.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub acs_transaction_id: Option<String>,
    /// Algorithm for generating a CAVV or UCAF data.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cavv_algorithm: Option<String>,
}

#[derive(Clone, Copy, Debug, Serialize)]
pub enum BarclaycardParesStatus {
    #[serde(rename = "C")]
    CardChallenged,
    #[serde(rename = "R")]
    AuthenticationRejected,
    #[serde(rename = "Y")]
    AuthenticationSuccessful,
    #[serde(rename = "A")]
    AuthenticationAttempted,
    #[serde(rename = "N")]
    AuthenticationFailed,
    #[serde(rename = "U")]
    AuthenticationNotCompleted,
}

impl From<TransactionStatus> for BarclaycardParesStatus {
    fn from(status: TransactionStatus) -> Self {
        match status {
            TransactionStatus::Success => Self::AuthenticationSuccessful,
            TransactionStatus::Failure => Self::AuthenticationFailed,
            TransactionStatus::VerificationNotPerformed => Self::AuthenticationNotCompleted,
            TransactionStatus::NotVerified => Self::AuthenticationAttempted,
            TransactionStatus::Rejected => Self::AuthenticationRejected,
            TransactionStatus::ChallengeRequired
            | TransactionStatus::ChallengeRequiredDecoupledAuthentication => Self::CardChallenged,
            TransactionStatus::InformationOnly => Self::AuthenticationNotCompleted,
        }
    }
}

// --- 3DS External Authentication (PreAuthenticate / Authenticate / PostAuthenticate) ---

/// `risk/v1/authentication-setups` request body (PreAuthenticate / device-data-collection setup).
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BarclaycardAuthSetupRequest<
    T: PaymentMethodDataTypes + Sync + Send + 'static + Serialize,
> {
    pub payment_information: PaymentInformation<T>,
    pub client_reference_information: ClientReferenceInformation,
}

/// `risk/v1/authentications` request body (Authenticate / enrollment check).
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BarclaycardAuthEnrollmentRequest<
    T: PaymentMethodDataTypes + Sync + Send + 'static + Serialize,
> {
    pub payment_information: PaymentInformation<T>,
    pub client_reference_information: ClientReferenceInformation,
    pub consumer_authentication_information: BarclaycardConsumerAuthInformationRequest,
    pub order_information: OrderInformationWithBill,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BarclaycardConsumerAuthInformationRequest {
    pub return_url: String,
    pub reference_id: String,
}

/// `risk/v1/authentication-results` request body (PostAuthenticate / validation).
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BarclaycardAuthValidateRequest<
    T: PaymentMethodDataTypes + Sync + Send + 'static + Serialize,
> {
    pub payment_information: PaymentInformation<T>,
    pub client_reference_information: ClientReferenceInformation,
    pub consumer_authentication_information: BarclaycardConsumerAuthInformationValidateRequest,
    pub order_information: OrderInformation,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BarclaycardConsumerAuthInformationValidateRequest {
    pub authentication_transaction_id: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProcessingInformation {
    pub commerce_indicator: String,
    pub capture: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub payment_solution: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cavv_algorithm: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CardPaymentInformation<T: PaymentMethodDataTypes + Sync + Send + 'static + Serialize> {
    pub card: Card<T>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum PaymentInformation<T: PaymentMethodDataTypes + Sync + Send + 'static + Serialize> {
    Cards(Box<CardPaymentInformation<T>>),
    ApplePayDecrypted(Box<ApplePayPaymentInformation>),
    ApplePayEncrypted(Box<ApplePayTokenPaymentInformation>),
    GooglePay(Box<GooglePayTokenPaymentInformation>),
}

/// Wallet transaction type.
///
/// Mirrors Cybersource: `"1"` (`InApp`) is used for tokenized in-app wallet payments
/// (ApplePay and GooglePay).
#[derive(Debug, Serialize)]
pub enum TransactionType {
    #[serde(rename = "1")]
    InApp,
}

/// ApplePay decrypted-token payment information.
///
/// Used when the network token (PAN + cryptogram) has been decrypted, so the card
/// details are sent directly under `tokenizedCard`.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayPaymentInformation {
    pub tokenized_card: TokenizedCard,
}

/// ApplePay encrypted-token payment information.
///
/// Used when only the encrypted Apple Pay blob is available; the blob is sent as
/// `fluidData` and Barclaycard performs the decryption.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayTokenPaymentInformation {
    pub fluid_data: FluidData,
    pub tokenized_card: ApplePayTokenizedCard,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenizedCard {
    pub number: cards::CardNumber,
    pub expiration_month: Secret<String>,
    pub expiration_year: Secret<String>,
    pub cryptogram: Secret<String>,
    pub transaction_type: TransactionType,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayTokenizedCard {
    pub transaction_type: TransactionType,
}

/// GooglePay encrypted-token payment information.
///
/// Used for the GooglePay wallet flow: the encrypted Google Pay token is sent as
/// `fluidData` and Barclaycard performs the decryption. Mirrors Cybersource's
/// `GooglePayTokenPaymentInformation`.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GooglePayTokenPaymentInformation {
    pub fluid_data: FluidData,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FluidData {
    pub value: Secret<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub descriptor: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Card<T: PaymentMethodDataTypes + Sync + Send + 'static + Serialize> {
    pub number: RawCardNumber<T>,
    pub expiration_month: Secret<String>,
    pub expiration_year: Secret<String>,
    pub security_code: Secret<String>,
    #[serde(rename = "type")]
    pub card_type: Option<String>,
    pub type_selection_indicator: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderInformationWithBill {
    pub amount_details: Amount,
    pub bill_to: Option<BillTo>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Amount {
    pub total_amount: StringMajorUnit,
    pub currency: common_enums::Currency,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BillTo {
    pub first_name: Secret<String>,
    pub last_name: Secret<String>,
    pub address1: Secret<String>,
    pub locality: String,
    pub administrative_area: Secret<String>,
    pub postal_code: Secret<String>,
    pub country: CountryAlpha2,
    pub email: pii::Email,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientReferenceInformation {
    pub code: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderInformation {
    pub amount_details: Amount,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BarclaycardCaptureRequest {
    pub order_information: OrderInformation,
    pub client_reference_information: ClientReferenceInformation,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub merchant_defined_information: Option<Vec<MerchantDefinedInformation>>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BarclaycardVoidRequest {
    pub client_reference_information: ClientReferenceInformation,
    pub reversal_information: ReversalInformation,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub merchant_defined_information: Option<Vec<MerchantDefinedInformation>>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ReversalInformation {
    pub amount_details: Amount,
    pub reason: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BarclaycardRefundRequest {
    pub order_information: OrderInformation,
    pub client_reference_information: ClientReferenceInformation,
}

// --- SetupMandate (Zero-dollar auth for TMS token creation) types ---

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BarclaycardSetupMandateRequest<
    T: PaymentMethodDataTypes + Sync + Send + 'static + Serialize,
> {
    pub processing_information: SetupMandateProcessingInformation,
    pub payment_information: PaymentInformation<T>,
    pub order_information: OrderInformationWithBill,
    pub client_reference_information: ClientReferenceInformation,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SetupMandateProcessingInformation {
    pub commerce_indicator: String,
    pub capture: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub action_list: Option<Vec<BarclaycardActionsList>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub action_token_types: Option<Vec<BarclaycardActionsTokenType>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub authorization_options: Option<SetupMandateAuthorizationOptions>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BarclaycardActionsList {
    TokenCreate,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum BarclaycardActionsTokenType {
    PaymentInstrument,
    Customer,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SetupMandateAuthorizationOptions {
    pub initiator: Option<SetupMandateInitiator>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SetupMandateInitiator {
    #[serde(rename = "type")]
    pub initiator_type: Option<BarclaycardPaymentInitiatorTypes>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub credential_stored_on_file: Option<bool>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum BarclaycardPaymentInitiatorTypes {
    Customer,
    Merchant,
}

// --- RepeatPayment (MIT) types ---

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BarclaycardRepeatPaymentRequest {
    pub processing_information: RepeatPaymentProcessingInformation,
    pub payment_information: RepeatPaymentInformation,
    pub order_information: OrderInformationWithBill,
    pub client_reference_information: ClientReferenceInformation,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub merchant_defined_information: Option<Vec<MerchantDefinedInformation>>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RepeatPaymentProcessingInformation {
    pub commerce_indicator: String,
    pub capture: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub authorization_options: Option<AuthorizationOptions>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthorizationOptions {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub initiator: Option<PaymentInitiator>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub merchant_initiated_transaction: Option<MerchantInitiatedTransaction>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentInitiator {
    #[serde(rename = "type")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub initiator_type: Option<BarclaycardPaymentInitiatorTypes>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stored_credential_used: Option<bool>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MerchantInitiatedTransaction {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reason: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub previous_transaction_id: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub original_authorized_amount: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum RepeatPaymentInformation {
    MandatePayment(Box<MandatePaymentInformation>),
    Cards(Box<CardWithNtiPaymentInformation>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CardWithNtiPaymentInformation {
    pub card: CardWithNti,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CardWithNti {
    pub number: cards::CardNumber,
    pub expiration_month: Secret<String>,
    pub expiration_year: Secret<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub security_code: Option<Secret<String>>,
    #[serde(rename = "type")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub card_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub type_selection_indicator: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MandatePaymentInformation {
    pub payment_instrument: PaymentInstrument,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub card: Option<MandateCard>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentInstrument {
    pub id: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MandateCard {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub type_selection_indicator: Option<String>,
}
