use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::canonical::CanonicalBillingDocument;
use crate::errors::BillingAdapterError;
use crate::{BillingConnectionMode, BillingProvider};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BillingProviderConnectionFieldDescriptor {
    pub key: String,
    pub value_type: String,
    pub description: String,
    pub required: bool,
    pub secret: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default_value: Option<Value>,
    #[serde(skip_serializing_if = "Vec::is_empty", default)]
    pub allowed_values: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub derived_from_connection_field: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BillingProviderConnectionConfigDescriptor {
    pub default_credential_kind: String,
    pub fields: Vec<BillingProviderConnectionFieldDescriptor>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BillingProviderDescriptor {
    pub provider: BillingProvider,
    pub implementation_status: String,
    pub default_api_base_url: String,
    pub signature_header_name: Option<String>,
    pub supported_webhook_envelope_kinds: Vec<String>,
    pub supported_webhook_event_types: Vec<String>,
    pub supported_credential_kinds: Vec<String>,
    pub supports_webhook_ingestion: bool,
    pub supports_canonical_projection: bool,
    pub supports_reconcile: bool,
    pub supported_reconcile_resource_kinds: Vec<String>,
    pub connection_config: BillingProviderConnectionConfigDescriptor,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub integration: Option<Value>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BillingWebhookEventSummary {
    pub provider: BillingProvider,
    pub envelope_kind: String,
    pub verification_mode: String,
    pub signature_header_name: Option<String>,
    pub resource: Option<String>,
    pub event_id: Option<String>,
    pub event_type: Option<String>,
    pub entity_id: Option<String>,
    pub created_at: Option<DateTime<Utc>>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BillingWebhookProjection {
    pub event: BillingWebhookEventSummary,
    pub payload: Value,
    pub document: Option<CanonicalBillingDocument>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BillingProviderResourceKind {
    Payment,
    PaymentLink,
    Subscription,
    Invoice,
}

impl BillingProviderResourceKind {
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Payment => "payment",
            Self::PaymentLink => "payment_link",
            Self::Subscription => "subscription",
            Self::Invoice => "invoice",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BillingProviderRefetchRequest {
    pub resource_kind: BillingProviderResourceKind,
    pub provider_ref: String,
    #[serde(default)]
    pub provider_customer_ref: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BillingProviderRefetchProjection {
    pub resource_kind: BillingProviderResourceKind,
    pub provider_ref: String,
    pub payload: Value,
    pub document: CanonicalBillingDocument,
}

#[async_trait]
pub trait BillingProviderAdapter: Send + Sync {
    fn provider(&self) -> BillingProvider;
    fn descriptor(&self) -> BillingProviderDescriptor;
    fn signature_header_name(&self) -> Option<&'static str>;
    fn normalize_connection_config(
        &self,
        config: &mut serde_json::Map<String, Value>,
        mode: BillingConnectionMode,
        credential_kind: &str,
        provider_profile_id: Option<&str>,
    );
    fn validate_connection_config(&self, config: &Value) -> Result<(), BillingAdapterError>;
    async fn project_webhook(
        &self,
        config: &Value,
        body: &[u8],
        signature_headers: &[String],
    ) -> Result<BillingWebhookProjection, BillingAdapterError>;
    async fn project_refetch(
        &self,
        config: &Value,
        request: &BillingProviderRefetchRequest,
    ) -> Result<BillingProviderRefetchProjection, BillingAdapterError>;
}

pub fn config_field(
    key: &str,
    value_type: &str,
    description: &str,
    required: bool,
) -> BillingProviderConnectionFieldDescriptor {
    BillingProviderConnectionFieldDescriptor {
        key: key.to_string(),
        value_type: value_type.to_string(),
        description: description.to_string(),
        required,
        secret: false,
        default_value: None,
        allowed_values: Vec::new(),
        derived_from_connection_field: None,
    }
}

impl BillingProviderConnectionFieldDescriptor {
    pub fn with_default(mut self, default_value: Value) -> Self {
        self.default_value = Some(default_value);
        self
    }

    pub fn with_allowed_values(mut self, allowed_values: Vec<String>) -> Self {
        self.allowed_values = allowed_values;
        self
    }

    pub fn with_derived_from(mut self, field: &str) -> Self {
        self.derived_from_connection_field = Some(field.to_string());
        self
    }

    pub fn with_secret(mut self, secret: bool) -> Self {
        self.secret = secret;
        self
    }
}
