use serde::{Deserialize, Serialize};
use serde_json::{Value, json};

use crate::BillingProvider;
use crate::canonical::{
    CanonicalBillingDocument, CanonicalInvoice, CanonicalPayment, CanonicalSubscription,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BillingSqlDocumentTable {
    Payments,
    Subscriptions,
    Invoices,
}

impl BillingSqlDocumentTable {
    pub const fn table_basename(self) -> &'static str {
        match self {
            Self::Payments => "billing_payments",
            Self::Subscriptions => "billing_subscriptions",
            Self::Invoices => "billing_invoices",
        }
    }

    pub const fn table_name(self) -> &'static str {
        match self {
            Self::Payments => "billing.billing_payments",
            Self::Subscriptions => "billing.billing_subscriptions",
            Self::Invoices => "billing.billing_invoices",
        }
    }

    pub fn qualified_table_name(self, schema: &str) -> String {
        format!("{schema}.{}", self.table_basename())
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BillingSqlDocumentWrite {
    pub table: BillingSqlDocumentTable,
    pub provider: BillingProvider,
    pub provider_ref: String,
    pub row: Value,
}

pub fn billing_sql_write_for_document(
    document: &CanonicalBillingDocument,
) -> BillingSqlDocumentWrite {
    match document {
        CanonicalBillingDocument::Payment(payment) => billing_sql_write_for_payment(payment),
        CanonicalBillingDocument::Subscription(subscription) => {
            billing_sql_write_for_subscription(subscription)
        }
        CanonicalBillingDocument::Invoice(invoice) => billing_sql_write_for_invoice(invoice),
    }
}

pub fn billing_sql_write_for_payment(payment: &CanonicalPayment) -> BillingSqlDocumentWrite {
    BillingSqlDocumentWrite {
        table: BillingSqlDocumentTable::Payments,
        provider: payment.provider,
        provider_ref: payment.provider_payment_id.clone(),
        row: json!({
            "provider": payment.provider.as_str(),
            "provider_payment_id": payment.provider_payment_id,
            "provider_customer_id": payment.provider_customer_id,
            "provider_profile_id": payment.provider_profile_id,
            "provider_subscription_id": payment.provider_subscription_id,
            "provider_payment_link_id": payment.provider_payment_link_id,
            "status": payment.status.as_str(),
            "amount_currency": payment.amount.currency,
            "amount_value": payment.amount.value,
            "amount_refunded_currency": payment.amount_refunded.as_ref().map(|amount| amount.currency.clone()),
            "amount_refunded_value": payment.amount_refunded.as_ref().map(|amount| amount.value.clone()),
            "description": payment.description,
            "metadata": payment.metadata,
            "raw": payment.raw,
            "paid_at": payment.paid_at,
            "created_at": payment.created_at,
        }),
    }
}

pub fn billing_sql_write_for_subscription(
    subscription: &CanonicalSubscription,
) -> BillingSqlDocumentWrite {
    BillingSqlDocumentWrite {
        table: BillingSqlDocumentTable::Subscriptions,
        provider: subscription.provider,
        provider_ref: subscription.provider_subscription_id.clone(),
        row: json!({
            "provider": subscription.provider.as_str(),
            "provider_subscription_id": subscription.provider_subscription_id,
            "provider_customer_id": subscription.provider_customer_id,
            "provider_profile_id": subscription.provider_profile_id,
            "status": subscription.status.as_str(),
            "amount_currency": subscription.amount.as_ref().map(|amount| amount.currency.clone()),
            "amount_value": subscription.amount.as_ref().map(|amount| amount.value.clone()),
            "interval": subscription.interval,
            "description": subscription.description,
            "metadata": subscription.metadata,
            "raw": subscription.raw,
            "next_payment_date": subscription.next_payment_date,
            "created_at": subscription.created_at,
            "canceled_at": subscription.canceled_at,
        }),
    }
}

pub fn billing_sql_write_for_invoice(invoice: &CanonicalInvoice) -> BillingSqlDocumentWrite {
    BillingSqlDocumentWrite {
        table: BillingSqlDocumentTable::Invoices,
        provider: invoice.provider,
        provider_ref: invoice.provider_invoice_id.clone(),
        row: json!({
            "provider": invoice.provider.as_str(),
            "provider_invoice_id": invoice.provider_invoice_id,
            "provider_profile_id": invoice.provider_profile_id,
            "provider_customer_id": invoice.provider_customer_id,
            "status": invoice.status.as_str(),
            "amount_currency": invoice.amount.as_ref().map(|amount| amount.currency.clone()),
            "amount_value": invoice.amount.as_ref().map(|amount| amount.value.clone()),
            "amount_paid_currency": invoice.amount_paid.as_ref().map(|amount| amount.currency.clone()),
            "amount_paid_value": invoice.amount_paid.as_ref().map(|amount| amount.value.clone()),
            "description": invoice.description,
            "metadata": invoice.metadata,
            "raw": invoice.raw,
            "issued_at": invoice.issued_at,
            "paid_at": invoice.paid_at,
            "created_at": invoice.created_at,
        }),
    }
}

#[cfg(test)]
mod tests {
    use super::{BillingSqlDocumentTable, billing_sql_write_for_document};
    use crate::canonical::{
        CanonicalBillingDocument, CanonicalInvoice, CanonicalMoney, CanonicalPayment,
        CanonicalSubscription,
    };
    use crate::{
        AthenaInvoiceStatus, AthenaPaymentStatus, AthenaSubscriptionStatus, BillingProvider,
    };
    use serde_json::json;

    #[test]
    fn payment_write_targets_billing_payments_table() {
        let write =
            billing_sql_write_for_document(&CanonicalBillingDocument::Payment(CanonicalPayment {
                provider: BillingProvider::Mollie,
                provider_payment_id: "pay_123".to_string(),
                provider_customer_id: Some("cst_123".to_string()),
                provider_profile_id: Some("pfl_123".to_string()),
                provider_subscription_id: Some("sub_123".to_string()),
                provider_payment_link_id: Some("pl_123".to_string()),
                status: AthenaPaymentStatus::Paid,
                amount: CanonicalMoney {
                    currency: "EUR".to_string(),
                    value: "24.95".to_string(),
                },
                amount_refunded: None,
                description: Some("Bike tires".to_string()),
                metadata: json!({"campaign":"spring"}),
                paid_at: None,
                created_at: None,
                raw: json!({"id":"pay_123"}),
            }));

        assert_eq!(write.table, BillingSqlDocumentTable::Payments);
        assert_eq!(write.provider_ref, "pay_123");
        assert_eq!(write.row["amount_value"], "24.95");
        assert_eq!(write.row["provider_subscription_id"], "sub_123");
        assert_eq!(write.row["provider_payment_link_id"], "pl_123");
    }

    #[test]
    fn subscription_and_invoice_writes_use_canonical_column_names() {
        let subscription_write = billing_sql_write_for_document(
            &CanonicalBillingDocument::Subscription(CanonicalSubscription {
                provider: BillingProvider::Stripe,
                provider_subscription_id: "sub_123".to_string(),
                provider_customer_id: "cus_123".to_string(),
                provider_profile_id: None,
                status: AthenaSubscriptionStatus::Active,
                amount: Some(CanonicalMoney {
                    currency: "USD".to_string(),
                    value: "10.00".to_string(),
                }),
                currency: Some("USD".to_string()),
                interval: Some("1 month".to_string()),
                description: Some("Starter".to_string()),
                metadata: json!({}),
                next_payment_date: Some("2026-07-01".to_string()),
                created_at: None,
                canceled_at: None,
                raw: json!({}),
            }),
        );
        let invoice_write =
            billing_sql_write_for_document(&CanonicalBillingDocument::Invoice(CanonicalInvoice {
                provider: BillingProvider::Stripe,
                provider_invoice_id: "inv_123".to_string(),
                provider_profile_id: None,
                provider_customer_id: Some("cus_123".to_string()),
                status: AthenaInvoiceStatus::Paid,
                amount: Some(CanonicalMoney {
                    currency: "USD".to_string(),
                    value: "10.00".to_string(),
                }),
                amount_paid: Some(CanonicalMoney {
                    currency: "USD".to_string(),
                    value: "10.00".to_string(),
                }),
                description: Some("Starter invoice".to_string()),
                metadata: json!({}),
                issued_at: None,
                paid_at: None,
                created_at: None,
                raw: json!({}),
            }));

        assert_eq!(
            subscription_write.table,
            BillingSqlDocumentTable::Subscriptions
        );
        assert_eq!(
            subscription_write.row["provider_subscription_id"],
            "sub_123"
        );
        assert_eq!(invoice_write.table, BillingSqlDocumentTable::Invoices);
        assert_eq!(invoice_write.row["amount_paid_value"], "10.00");
    }

    #[test]
    fn table_helpers_expose_default_and_schema_scoped_names() {
        assert_eq!(
            BillingSqlDocumentTable::Payments.table_basename(),
            "billing_payments"
        );
        assert_eq!(
            BillingSqlDocumentTable::Payments.table_name(),
            "billing.billing_payments"
        );
        assert_eq!(
            BillingSqlDocumentTable::Payments.qualified_table_name("billing"),
            "billing.billing_payments"
        );
    }
}
