use common_types::{customers::DocumentKind, primitive_wrappers::CustomerListLimit};
use common_utils::{
    crypto, custom_serde,
    errors::{ParsingError, ValidationError},
    ext_traits::{Encode, ValueExt},
    id_type, pii,
    types::Description,
};
use hyperswitch_masking::{PeekInterface, Secret};
use serde::{Deserialize, Serialize};
use smithy::SmithyModel;
use utoipa::ToSchema;

use crate::payments;

#[cfg(feature = "v2")]
pub mod migrate;

/// The customer details
#[cfg(feature = "v1")]
#[derive(Debug, Default, Clone, Deserialize, Serialize, ToSchema, SmithyModel)]
#[smithy(namespace = "com.hyperswitch.smithy.types")]
pub struct CustomerRequest {
    /// The identifier for the customer object. If not provided the customer ID will be autogenerated.
    #[schema(value_type = Option<String>, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
    #[smithy(value_type = "Option<String>")]
    pub customer_id: Option<id_type::CustomerId>,
    /// The identifier for the Merchant Account
    #[schema(max_length = 255, example = "y3oqhf46pyzuxjbcn2giaqnb44")]
    #[serde(skip)]
    pub merchant_id: id_type::MerchantId,
    /// The customer's name
    #[schema(max_length = 255, value_type = Option<String>, example = "Jon Test")]
    #[smithy(value_type = "Option<String>")]
    pub name: Option<Secret<String>>,
    /// The customer's email address
    #[schema(value_type = Option<String>, max_length = 255, example = "JonTest@test.com")]
    #[smithy(value_type = "Option<String>")]
    pub email: Option<pii::Email>,
    /// The customer's phone number
    #[schema(value_type = Option<String>, max_length = 255, example = "9123456789")]
    #[smithy(value_type = "Option<String>")]
    pub phone: Option<Secret<String>>,
    /// An arbitrary string that you can attach to a customer object.
    #[schema(max_length = 255, example = "First Customer", value_type = Option<String>)]
    #[smithy(value_type = "Option<String>")]
    pub description: Option<Description>,
    /// The country code for the customer phone number
    #[schema(max_length = 255, example = "+65")]
    #[smithy(value_type = "Option<String>")]
    pub phone_country_code: Option<String>,
    /// The address for the customer
    #[schema(value_type = Option<AddressDetails>)]
    #[smithy(value_type = "Option<AddressDetails>")]
    pub address: Option<payments::AddressDetails>,
    /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500
    /// characters long. Metadata is useful for storing additional, structured information on an
    /// object.
    #[schema(value_type = Option<Object>,example = json!({ "city": "NY", "unit": "245" }))]
    #[smithy(value_type = "Option<Object>")]
    pub metadata: Option<pii::SecretSerdeValue>,
    /// Customer's tax registration ID
    #[schema(max_length = 255, value_type = Option<String>, example = "123456789")]
    #[smithy(value_type = "Option<String>")]
    pub tax_registration_id: Option<Secret<String>>,
    /// Customer’s country-specific identification number and type used for regulatory or tax purposes
    #[schema(value_type = Option<CustomerDocumentDetails>)]
    #[smithy(value_type = "Option<CustomerDocumentDetails>")]
    pub document_details: Option<CustomerDocumentDetails>,
}

#[derive(Debug, Default, Clone, Deserialize, Serialize, ToSchema, SmithyModel)]
#[smithy(namespace = "com.hyperswitch.smithy.types", mixin = true)]
pub struct CustomerListRequest {
    /// Offset
    #[schema(example = 32)]
    #[smithy(value_type = "Option<u32>", http_query = "offset")]
    pub offset: Option<u32>,
    /// Limit
    #[schema(example = 32)]
    #[smithy(value_type = "Option<u16>", http_query = "limit")]
    pub limit: Option<u16>,
    pub customer_id: Option<id_type::CustomerId>,
}

#[derive(Debug, Default, Clone, Deserialize, Serialize, ToSchema)]
pub struct CustomerListRequestWithConstraints {
    /// Offset
    #[schema(example = 32)]
    pub offset: Option<u32>,
    /// Limit
    #[schema(example = 32)]
    pub limit: Option<CustomerListLimit>,
    /// Unique identifier for a customer
    pub customer_id: Option<id_type::CustomerId>,
    /// Filter with created time range
    #[serde(flatten)]
    pub time_range: Option<common_utils::types::TimeRange>,
}

#[cfg(feature = "v1")]
impl CustomerRequest {
    pub fn get_merchant_reference_id(&self) -> Option<id_type::CustomerId> {
        Some(
            self.customer_id
                .to_owned()
                .unwrap_or_else(common_utils::generate_customer_id_of_default_length),
        )
    }
    pub fn get_address(&self) -> Option<payments::AddressDetails> {
        self.address.clone()
    }
    pub fn get_optional_email(&self) -> Option<pii::Email> {
        self.email.clone()
    }
    pub fn validate_document_details(
        &self,
    ) -> common_utils::errors::CustomResult<(), ValidationError> {
        self.document_details
            .as_ref()
            .map(|doc| doc.validate())
            .unwrap_or(Ok(()))
    }
}

/// The customer details
#[cfg(feature = "v2")]
#[derive(Debug, Default, Clone, Deserialize, Serialize, ToSchema)]
#[serde(deny_unknown_fields)]
pub struct CustomerRequest {
    /// The merchant identifier for the customer object.
    #[schema(value_type = Option<String>, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
    pub merchant_reference_id: Option<id_type::CustomerId>,
    /// The customer's name
    #[schema(max_length = 255, value_type = String, example = "Jon Test")]
    pub name: Secret<String>,
    /// The customer's email address
    #[schema(value_type = String, max_length = 255, example = "JonTest@test.com")]
    pub email: pii::Email,
    /// The customer's phone number
    #[schema(value_type = Option<String>, max_length = 255, example = "9123456789")]
    pub phone: Option<Secret<String>>,
    /// An arbitrary string that you can attach to a customer object.
    #[schema(max_length = 255, example = "First Customer", value_type = Option<String>)]
    pub description: Option<Description>,
    /// The country code for the customer phone number
    #[schema(max_length = 255, example = "+65")]
    pub phone_country_code: Option<String>,
    /// The default billing address for the customer
    #[schema(value_type = Option<AddressDetails>)]
    pub default_billing_address: Option<payments::AddressDetails>,
    /// The default shipping address for the customer
    #[schema(value_type = Option<AddressDetails>)]
    pub default_shipping_address: Option<payments::AddressDetails>,
    /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500
    /// characters long. Metadata is useful for storing additional, structured information on an
    /// object.
    #[schema(value_type = Option<Object>,example = json!({ "city": "NY", "unit": "245" }))]
    pub metadata: Option<pii::SecretSerdeValue>,
    /// The customer's tax registration number.
    #[schema(max_length = 255, value_type = Option<String>, example = "123456789")]
    pub tax_registration_id: Option<Secret<String>>,
    /// Customer’s country-specific identification number and type used for regulatory or tax purposes
    #[schema(value_type = Option<CustomerDocumentDetails>)]
    pub document_details: Option<CustomerDocumentDetails>,
}

#[cfg(feature = "v2")]
impl CustomerRequest {
    pub fn get_merchant_reference_id(&self) -> Option<id_type::CustomerId> {
        self.merchant_reference_id.clone()
    }

    pub fn get_default_customer_billing_address(&self) -> Option<payments::AddressDetails> {
        self.default_billing_address.clone()
    }

    pub fn get_default_customer_shipping_address(&self) -> Option<payments::AddressDetails> {
        self.default_shipping_address.clone()
    }

    pub fn get_optional_email(&self) -> Option<pii::Email> {
        Some(self.email.clone())
    }
}

#[cfg(feature = "v1")]
#[derive(Debug, Clone, Serialize, ToSchema, SmithyModel)]
#[smithy(namespace = "com.hyperswitch.smithy.types")]
pub struct CustomerResponse {
    /// The identifier for the customer object
    #[schema(value_type = String, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
    #[smithy(value_type = "String")]
    pub customer_id: id_type::CustomerId,
    /// The customer's name
    #[schema(max_length = 255, value_type = Option<String>, example = "Jon Test")]
    #[smithy(value_type = "Option<String>")]
    pub name: crypto::OptionalEncryptableName,
    /// The customer's email address
    #[schema(value_type = Option<String>,max_length = 255, example = "JonTest@test.com")]
    #[smithy(value_type = "Option<String>")]
    pub email: crypto::OptionalEncryptableEmail,
    /// The customer's phone number
    #[schema(value_type = Option<String>,max_length = 255, example = "9123456789")]
    #[smithy(value_type = "Option<String>")]
    pub phone: crypto::OptionalEncryptablePhone,
    /// The country code for the customer phone number
    #[schema(max_length = 255, example = "+65")]
    #[smithy(value_type = "Option<String>")]
    pub phone_country_code: Option<String>,
    /// An arbitrary string that you can attach to a customer object.
    #[schema(max_length = 255, example = "First Customer", value_type = Option<String>)]
    #[smithy(value_type = "Option<String>")]
    pub description: Option<Description>,
    /// The address for the customer
    #[schema(value_type = Option<AddressDetails>)]
    #[smithy(value_type = "Option<AddressDetails>")]
    pub address: Option<payments::AddressDetails>,
    ///  A timestamp (ISO 8601 code) that determines when the customer was created
    #[schema(value_type = PrimitiveDateTime,example = "2023-01-18T11:04:09.922Z")]
    #[serde(with = "custom_serde::iso8601")]
    #[smithy(value_type = "Option<String>")]
    pub created_at: time::PrimitiveDateTime,
    /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500
    /// characters long. Metadata is useful for storing additional, structured information on an
    /// object.
    #[schema(value_type = Option<Object>,example = json!({ "city": "NY", "unit": "245" }))]
    #[smithy(value_type = "Option<Object>")]
    pub metadata: Option<pii::SecretSerdeValue>,
    /// The identifier for the default payment method.
    #[schema(max_length = 64, example = "pm_djh2837dwduh890123")]
    #[smithy(value_type = "Option<String>")]
    pub default_payment_method_id: Option<String>,
    /// The customer's tax registration number.
    #[schema(max_length = 255, value_type = Option<String>, example = "123456789")]
    #[smithy(value_type = "Option<String>")]
    pub tax_registration_id: crypto::OptionalEncryptableSecretString,
    /// Customer’s country-specific identification number and type used for regulatory or tax purposes
    #[schema(value_type = Option<CustomerDocumentDetails>)]
    #[smithy(value_type = "Option<CustomerDocumentDetails>")]
    pub document_details: Option<CustomerDocumentDetails>,
}

#[cfg(feature = "v1")]
impl CustomerResponse {
    pub fn get_merchant_reference_id(&self) -> Option<id_type::CustomerId> {
        Some(self.customer_id.clone())
    }
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct CustomerResponse {
    /// Unique identifier for the customer
    #[schema(
        min_length = 32,
        max_length = 64,
        example = "0a_cus_01926c58bc6e77c09e809964e72af8c8",
        value_type = String
    )]
    pub id: id_type::GlobalCustomerId,
    /// The identifier for the customer object
    #[schema(value_type = String, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
    pub merchant_reference_id: Option<id_type::CustomerId>,
    /// Connector specific customer reference ids
    #[schema(value_type = Option<Object>, example = json!({"mca_hwySG2NtpzX0qr7toOy8": "cus_Rnm2pDKGyQi506"}))]
    pub connector_customer_ids: Option<common_types::customers::ConnectorCustomerMap>,
    /// The customer's name
    #[schema(max_length = 255, value_type = Option<String>, example = "Jon Test")]
    pub name: crypto::OptionalEncryptableName,
    /// The customer's email address
    #[schema(value_type = Option<String> ,max_length = 255, example = "JonTest@test.com")]
    pub email: crypto::OptionalEncryptableEmail,
    /// The customer's phone number
    #[schema(value_type = Option<String>,max_length = 255, example = "9123456789")]
    pub phone: crypto::OptionalEncryptablePhone,
    /// The country code for the customer phone number
    #[schema(max_length = 255, example = "+65")]
    pub phone_country_code: Option<String>,
    /// An arbitrary string that you can attach to a customer object.
    #[schema(max_length = 255, example = "First Customer", value_type = Option<String>)]
    pub description: Option<Description>,
    /// The default billing address for the customer
    #[schema(value_type = Option<AddressDetails>)]
    pub default_billing_address: Option<payments::AddressDetails>,
    /// The default shipping address for the customer
    #[schema(value_type = Option<AddressDetails>)]
    pub default_shipping_address: Option<payments::AddressDetails>,
    ///  A timestamp (ISO 8601 code) that determines when the customer was created
    #[schema(value_type = PrimitiveDateTime,example = "2023-01-18T11:04:09.922Z")]
    #[serde(with = "custom_serde::iso8601")]
    pub created_at: time::PrimitiveDateTime,
    /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500
    /// characters long. Metadata is useful for storing additional, structured information on an
    /// object.
    #[schema(value_type = Option<Object>,example = json!({ "city": "NY", "unit": "245" }))]
    pub metadata: Option<pii::SecretSerdeValue>,
    /// The identifier for the default payment method.
    #[schema(value_type = Option<String>, max_length = 64, example = "0a_pm_01926c58bc6e77c09e809964e72af8c8")]
    pub default_payment_method_id: Option<id_type::GlobalPaymentMethodId>,
    /// The customer's tax registration number.
    #[schema(max_length = 255, value_type = Option<String>, example = "123456789")]
    pub tax_registration_id: crypto::OptionalEncryptableSecretString,
    /// Customer’s country-specific identification number and type used for regulatory or tax purposes
    #[schema(value_type = Option<CustomerDocumentDetails>)]
    pub document_details: Option<CustomerDocumentDetails>,
}

#[cfg(feature = "v2")]
impl CustomerResponse {
    pub fn get_merchant_reference_id(&self) -> Option<id_type::CustomerId> {
        self.merchant_reference_id.clone()
    }
}

#[cfg(feature = "v1")]
#[derive(Debug, Deserialize, Serialize, ToSchema, SmithyModel)]
#[smithy(namespace = "com.hyperswitch.smithy.types")]
pub struct CustomerDeleteResponse {
    /// The identifier for the customer object
    #[schema(value_type = String, max_length = 255, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
    #[smithy(value_type = "String")]
    pub customer_id: id_type::CustomerId,
    /// Whether customer was deleted or not
    #[schema(example = false)]
    #[smithy(value_type = "bool")]
    pub customer_deleted: bool,
    /// Whether address was deleted or not
    #[schema(example = false)]
    #[smithy(value_type = "bool")]
    pub address_deleted: bool,
    /// Whether payment methods deleted or not
    #[schema(example = false)]
    #[smithy(value_type = "bool")]
    pub payment_methods_deleted: bool,
}

#[cfg(feature = "v2")]
#[derive(Debug, Deserialize, Serialize, ToSchema)]
pub struct CustomerDeleteResponse {
    /// Unique identifier for the customer
    #[schema(
        min_length = 32,
        max_length = 64,
        example = "0a_cus_01926c58bc6e77c09e809964e72af8c8",
        value_type = String
    )]
    pub id: id_type::GlobalCustomerId,
    /// The identifier for the customer object
    #[schema(value_type = String, max_length = 255, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
    pub merchant_reference_id: Option<id_type::CustomerId>,
    /// Whether customer was deleted or not
    #[schema(example = false)]
    pub customer_deleted: bool,
    /// Whether address was deleted or not
    #[schema(example = false)]
    pub address_deleted: bool,
    /// Whether payment methods deleted or not
    #[schema(example = false)]
    pub payment_methods_deleted: bool,
}

/// The identifier for the customer object. If not provided the customer ID will be autogenerated.
#[cfg(feature = "v1")]
#[derive(Debug, Default, Clone, Deserialize, Serialize, ToSchema, SmithyModel)]
#[smithy(namespace = "com.hyperswitch.smithy.types")]
pub struct CustomerUpdateRequest {
    /// The identifier for the Merchant Account
    #[schema(max_length = 255, example = "y3oqhf46pyzuxjbcn2giaqnb44")]
    #[serde(skip)]
    pub merchant_id: id_type::MerchantId,
    /// The customer's name
    #[schema(max_length = 255, value_type = Option<String>, example = "Jon Test")]
    #[smithy(value_type = "Option<String>")]
    pub name: Option<Secret<String>>,
    /// The customer's email address
    #[schema(value_type = Option<String>, max_length = 255, example = "JonTest@test.com")]
    #[smithy(value_type = "Option<String>")]
    pub email: Option<pii::Email>,
    /// The customer's phone number
    #[schema(value_type = Option<String>, max_length = 255, example = "9123456789")]
    #[smithy(value_type = "Option<String>")]
    pub phone: Option<Secret<String>>,
    /// An arbitrary string that you can attach to a customer object.
    #[schema(max_length = 255, example = "First Customer", value_type = Option<String>)]
    #[smithy(value_type = "Option<String>")]
    pub description: Option<Description>,
    /// The country code for the customer phone number
    #[schema(max_length = 255, example = "+65")]
    #[smithy(value_type = "Option<String>")]
    pub phone_country_code: Option<String>,
    /// The address for the customer
    #[schema(value_type = Option<AddressDetails>)]
    #[smithy(value_type = "Option<AddressDetails>")]
    pub address: Option<payments::AddressDetails>,
    /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500
    /// characters long. Metadata is useful for storing additional, structured information on an
    /// object.
    #[schema(value_type = Option<Object>,example = json!({ "city": "NY", "unit": "245" }))]
    #[smithy(value_type = "Option<Object>")]
    pub metadata: Option<pii::SecretSerdeValue>,
    /// Customer's tax registration ID
    #[schema(max_length = 255, value_type = Option<String>, example = "123456789")]
    #[smithy(value_type = "Option<String>")]
    pub tax_registration_id: Option<Secret<String>>,
    /// Customer’s country-specific identification number and type used for regulatory or tax purposes
    #[schema(value_type = Option<CustomerDocumentDetails>)]
    #[smithy(value_type = "Option<CustomerDocumentDetails>")]
    pub document_details: Option<CustomerDocumentDetails>,
}

#[cfg(feature = "v1")]
impl CustomerUpdateRequest {
    pub fn get_address(&self) -> Option<payments::AddressDetails> {
        self.address.clone()
    }
}

#[cfg(feature = "v2")]
#[derive(Debug, Default, Clone, Deserialize, Serialize, ToSchema)]
#[serde(deny_unknown_fields)]
pub struct CustomerUpdateRequest {
    /// The customer's name
    #[schema(max_length = 255, value_type = String, example = "Jon Test")]
    pub name: Option<Secret<String>>,
    /// The customer's email address
    #[schema(value_type = String, max_length = 255, example = "JonTest@test.com")]
    pub email: Option<pii::Email>,
    /// The customer's phone number
    #[schema(value_type = Option<String>, max_length = 255, example = "9123456789")]
    pub phone: Option<Secret<String>>,
    /// An arbitrary string that you can attach to a customer object.
    #[schema(max_length = 255, example = "First Customer", value_type = Option<String>)]
    pub description: Option<Description>,
    /// The country code for the customer phone number
    #[schema(max_length = 255, example = "+65")]
    pub phone_country_code: Option<String>,
    /// The default billing address for the customer
    #[schema(value_type = Option<AddressDetails>)]
    pub default_billing_address: Option<payments::AddressDetails>,
    /// The default shipping address for the customer
    #[schema(value_type = Option<AddressDetails>)]
    pub default_shipping_address: Option<payments::AddressDetails>,
    /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500
    /// characters long. Metadata is useful for storing additional, structured information on an
    /// object.
    #[schema(value_type = Option<Object>,example = json!({ "city": "NY", "unit": "245" }))]
    pub metadata: Option<pii::SecretSerdeValue>,
    /// The unique identifier of the payment method
    #[schema(value_type = Option<String>, example = "0a_pm_01926c58bc6e77c09e809964e72af8c8")]
    pub default_payment_method_id: Option<id_type::GlobalPaymentMethodId>,
    /// The customer's tax registration number.
    #[schema(max_length = 255, value_type = Option<String>, example = "123456789")]
    pub tax_registration_id: Option<Secret<String>>,
    /// Customer’s country-specific identification number and type used for regulatory or tax purposes
    #[schema(value_type = Option<CustomerDocumentDetails>)]
    pub document_details: Option<CustomerDocumentDetails>,
}

#[cfg(feature = "v2")]
impl CustomerUpdateRequest {
    pub fn get_default_customer_billing_address(&self) -> Option<payments::AddressDetails> {
        self.default_billing_address.clone()
    }

    pub fn get_default_customer_shipping_address(&self) -> Option<payments::AddressDetails> {
        self.default_shipping_address.clone()
    }
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize)]
pub struct CustomerUpdateRequestInternal {
    pub customer_id: id_type::CustomerId,
    pub request: CustomerUpdateRequest,
}

#[cfg(feature = "v2")]
#[derive(Debug, Serialize)]
pub struct CustomerUpdateRequestInternal {
    pub id: id_type::GlobalCustomerId,
    pub request: CustomerUpdateRequest,
}

#[derive(Debug, Serialize, ToSchema)]
pub struct CustomerListResponse {
    /// List of customers
    pub data: Vec<CustomerResponse>,
    /// Total count of customers
    pub total_count: usize,
}

#[derive(Debug, Clone, Deserialize, Serialize, ToSchema, PartialEq)]
pub struct CustomerDocumentDetails {
    /// The customer's document type
    #[schema(value_type = DocumentKind, example = "cpf")]
    pub document_type: DocumentKind,
    /// The customer's document number
    /// Length of the document number depends upon the document_type.
    /// For CPF/CNPJ it is typically 11/14 digits long
    #[schema(max_length = 255, value_type = String, example = "12345678911")]
    pub document_number: Secret<String>,
}

impl CustomerDocumentDetails {
    pub fn from(
        value: &Option<pii::SecretSerdeValue>,
    ) -> common_utils::errors::CustomResult<Option<Self>, ParsingError> {
        value
            .as_ref()
            .map(|pii_value| {
                pii_value
                    .peek()
                    .clone()
                    .parse_value::<Self>("CustomerDocumentDetails")
            })
            .transpose()
    }
    pub fn to(&self) -> common_utils::errors::CustomResult<pii::SecretSerdeValue, ParsingError> {
        self.encode_to_value().map(Secret::new)
    }

    pub fn validate(&self) -> common_utils::errors::CustomResult<(), ValidationError> {
        self.document_type.validate(self.document_number.peek())
    }
}
