pub use athena_billing_core::canonical;
pub mod contracts;
pub use athena_billing_core::errors;
pub mod grants;
pub mod providers;
pub use athena_billing_core::rights_registry;
pub mod sinks;
pub mod storage;

use std::collections::{BTreeMap, BTreeSet};

use athena_billing_core::rights_registry::{
    BillingDialectRightDescriptor, BillingDialectRightGroup, BillingTranslatedRightDescriptor,
    BillingTranslatedRightProviderBinding,
};
pub use athena_billing_core::{
    AthenaInvoiceStatus, AthenaPaymentStatus, AthenaSubscriptionStatus,
    BillingCapabilitiesDocument, BillingConnectionMode, BillingConnectionStatus,
    BillingFeatureFlags, BillingOwnerKind, BillingProvider, BillingProviderConnection,
    BillingSubjectRef,
};

use crate::grants::{
    BILLING_CUSTOMER_MANAGE_GRANT, BILLING_DOCUMENT_READ_GRANT, BILLING_MANAGE_GRANT,
    BILLING_PROVIDER_ADMIN_GRANT, BILLING_READ_GRANT, BILLING_REFUND_MANAGE_GRANT,
    BILLING_SUBSCRIPTION_MANAGE_GRANT, INVOICE_OPEN_GRANT, INVOICE_PAID_GRANT,
    PAYMENT_FAILED_GRANT, PAYMENT_LINK_FAILED_GRANT, PAYMENT_LINK_PAID_GRANT, PAYMENT_PAID_GRANT,
    PAYMENT_REFUNDED_GRANT, SUBSCRIPTION_ACTIVE_GRANT, SUBSCRIPTION_CANCELED_GRANT,
    SUBSCRIPTION_PAST_DUE_GRANT,
};

pub fn billing_dialect_rights(
    provider: BillingProvider,
) -> &'static [BillingDialectRightDescriptor] {
    match provider {
        BillingProvider::Mollie => athena_billing_mollie::mollie_dialect_rights(),
        BillingProvider::Stripe => athena_billing_stripe::stripe_dialect_rights(),
    }
}

pub fn billing_dialect_right_groups() -> Vec<BillingDialectRightGroup> {
    [BillingProvider::Mollie, BillingProvider::Stripe]
        .into_iter()
        .map(|provider| BillingDialectRightGroup {
            provider,
            rights: billing_dialect_rights(provider).to_vec(),
        })
        .collect()
}

pub fn billing_translated_rights() -> Vec<BillingTranslatedRightDescriptor> {
    let mut grouped = BTreeMap::<String, BillingTranslatedRightDescriptor>::new();

    for provider in [BillingProvider::Mollie, BillingProvider::Stripe] {
        for right in billing_dialect_rights(provider) {
            grouped
                .entry(right.canonical_key.to_string())
                .and_modify(|descriptor| {
                    descriptor
                        .providers
                        .push(BillingTranslatedRightProviderBinding {
                            provider: right.provider,
                            vendor_key: right.vendor_key.to_string(),
                        });
                })
                .or_insert_with(|| BillingTranslatedRightDescriptor {
                    key: right.canonical_key.to_string(),
                    resource: right.resource.to_string(),
                    action: right.action.to_string(),
                    description: right.description.to_string(),
                    providers: vec![BillingTranslatedRightProviderBinding {
                        provider: right.provider,
                        vendor_key: right.vendor_key.to_string(),
                    }],
                });
        }
    }

    let mut rights = grouped.into_values().collect::<Vec<_>>();
    rights.sort_by(|left, right| left.key.cmp(&right.key));
    for right in &mut rights {
        right.providers.sort_by(|left, right| {
            left.provider
                .as_str()
                .cmp(right.provider.as_str())
                .then_with(|| left.vendor_key.cmp(&right.vendor_key))
        });
    }
    rights
}

pub fn resolve_billing_translated_rights(
    provider: BillingProvider,
    scopes: &[String],
) -> Vec<String> {
    let scope_set = normalize_scope_set(scopes);
    let mut translated = billing_dialect_rights(provider)
        .iter()
        .filter(|descriptor| scope_set.contains(descriptor.vendor_key))
        .map(|descriptor| descriptor.canonical_key.to_string())
        .collect::<BTreeSet<_>>()
        .into_iter()
        .collect::<Vec<_>>();
    translated.sort();
    translated
}

pub fn capabilities_from_connection(
    connection: &BillingProviderConnection,
) -> BillingCapabilitiesDocument {
    let resolved =
        resolve_billing_dialect_right_descriptors(connection.provider, &connection.scopes);
    let features = features_from_translated_rights(&resolved);
    let claims = claims_from_translated_rights(&resolved);

    BillingCapabilitiesDocument {
        provider: connection.provider,
        connected: connection.status.is_connected(),
        mode: connection.mode,
        status: connection.status,
        provider_account_id: connection.provider_account_id.clone(),
        provider_profile_id: connection.provider_profile_id.clone(),
        features,
        claims,
    }
}

pub fn grant_keys_for_payment_status(
    status: AthenaPaymentStatus,
    is_payment_link: bool,
) -> Vec<&'static str> {
    let mut grants = Vec::new();

    match status {
        AthenaPaymentStatus::Paid => {
            grants.push(PAYMENT_PAID_GRANT.key);
            if is_payment_link {
                grants.push(PAYMENT_LINK_PAID_GRANT.key);
            }
        }
        AthenaPaymentStatus::Failed => {
            grants.push(PAYMENT_FAILED_GRANT.key);
            if is_payment_link {
                grants.push(PAYMENT_LINK_FAILED_GRANT.key);
            }
        }
        AthenaPaymentStatus::Refunded => grants.push(PAYMENT_REFUNDED_GRANT.key),
        AthenaPaymentStatus::Pending
        | AthenaPaymentStatus::Authorized
        | AthenaPaymentStatus::Canceled => {}
    }

    grants
}

pub fn grant_keys_for_subscription_status(status: AthenaSubscriptionStatus) -> Vec<&'static str> {
    match status {
        AthenaSubscriptionStatus::Active => vec![SUBSCRIPTION_ACTIVE_GRANT.key],
        AthenaSubscriptionStatus::Canceled => vec![SUBSCRIPTION_CANCELED_GRANT.key],
        AthenaSubscriptionStatus::PastDue => vec![SUBSCRIPTION_PAST_DUE_GRANT.key],
        AthenaSubscriptionStatus::Trialing | AthenaSubscriptionStatus::Paused => Vec::new(),
    }
}

pub fn grant_keys_for_invoice_status(status: AthenaInvoiceStatus) -> Vec<&'static str> {
    match status {
        AthenaInvoiceStatus::Open => vec![INVOICE_OPEN_GRANT.key],
        AthenaInvoiceStatus::Paid => vec![INVOICE_PAID_GRANT.key],
        AthenaInvoiceStatus::Draft
        | AthenaInvoiceStatus::Void
        | AthenaInvoiceStatus::Uncollectible => Vec::new(),
    }
}

fn normalize_scope_set(scopes: &[String]) -> BTreeSet<String> {
    scopes
        .iter()
        .map(|scope| scope.trim().to_ascii_lowercase())
        .filter(|scope| !scope.is_empty())
        .collect()
}

fn resolve_billing_dialect_right_descriptors(
    provider: BillingProvider,
    scopes: &[String],
) -> Vec<&'static BillingDialectRightDescriptor> {
    let scope_set = normalize_scope_set(scopes);
    let mut resolved = billing_dialect_rights(provider)
        .iter()
        .filter(|descriptor| scope_set.contains(descriptor.vendor_key))
        .collect::<Vec<_>>();
    resolved.sort_by(|left, right| left.canonical_key.cmp(right.canonical_key));
    resolved
}

fn features_from_translated_rights(
    rights: &[&BillingDialectRightDescriptor],
) -> BillingFeatureFlags {
    BillingFeatureFlags {
        payments: has_resource(rights, "billing.payments"),
        subscriptions: has_resource(rights, "billing.subscriptions"),
        refunds: has_resource(rights, "billing.refunds")
            || has_right(rights, "billing.payments", "write"),
        documents: has_resource(rights, "billing.sales-invoices")
            || has_resource(rights, "billing.invoices")
            || has_resource(rights, "billing.documents"),
        mandates: has_resource(rights, "billing.mandates"),
        sales_invoices: has_resource(rights, "billing.sales-invoices"),
    }
}

fn claims_from_translated_rights(rights: &[&BillingDialectRightDescriptor]) -> Vec<String> {
    let mut claims = BTreeSet::new();
    let has_read = rights
        .iter()
        .any(|right| matches!(right.action, "read" | "write"));
    let has_write = rights.iter().any(|right| right.action == "write");

    if has_read {
        claims.insert(BILLING_READ_GRANT.key.to_string());
    }
    if has_write {
        claims.insert(BILLING_MANAGE_GRANT.key.to_string());
    }
    if has_right(rights, "billing.customers", "write") {
        claims.insert(BILLING_CUSTOMER_MANAGE_GRANT.key.to_string());
    }
    if has_right(rights, "billing.subscriptions", "write") {
        claims.insert(BILLING_SUBSCRIPTION_MANAGE_GRANT.key.to_string());
    }
    if has_right(rights, "billing.refunds", "write")
        || has_right(rights, "billing.payments", "write")
    {
        claims.insert(BILLING_REFUND_MANAGE_GRANT.key.to_string());
    }
    if has_any_resource(
        rights,
        &[
            "billing.sales-invoices",
            "billing.invoices",
            "billing.documents",
        ],
    ) {
        claims.insert(BILLING_DOCUMENT_READ_GRANT.key.to_string());
    }
    if has_any_resource(
        rights,
        &[
            "billing.profiles",
            "billing.webhooks",
            "billing.permissions",
            "billing.accounts",
        ],
    ) {
        claims.insert(BILLING_PROVIDER_ADMIN_GRANT.key.to_string());
    }

    claims.into_iter().collect()
}

fn has_resource(rights: &[&BillingDialectRightDescriptor], resource: &str) -> bool {
    rights.iter().any(|right| right.resource == resource)
}

fn has_any_resource(rights: &[&BillingDialectRightDescriptor], resources: &[&str]) -> bool {
    rights
        .iter()
        .any(|right| resources.iter().any(|resource| *resource == right.resource))
}

fn has_right(rights: &[&BillingDialectRightDescriptor], resource: &str, action: &str) -> bool {
    rights
        .iter()
        .any(|right| right.resource == resource && right.action == action)
}

#[cfg(test)]
mod tests {
    use super::{
        AthenaInvoiceStatus, AthenaPaymentStatus, AthenaSubscriptionStatus, BillingConnectionMode,
        BillingConnectionStatus, BillingProvider, BillingProviderConnection,
        billing_translated_rights, capabilities_from_connection, grant_keys_for_invoice_status,
        grant_keys_for_payment_status, grant_keys_for_subscription_status,
        resolve_billing_translated_rights,
    };
    use crate::grants::{
        BILLING_GRANT_CATALOG, billing_grant_by_key, billing_permission_object_for_grants,
    };
    use chrono::Utc;
    use serde_json::json;
    use uuid::Uuid;

    #[test]
    fn mollie_scopes_expand_to_capabilities_and_claims() {
        let connection = BillingProviderConnection {
            id: Uuid::new_v4(),
            provider: BillingProvider::Mollie,
            provider_account_id: "org_123".to_string(),
            provider_profile_id: Some("pfl_123".to_string()),
            mode: BillingConnectionMode::Live,
            status: BillingConnectionStatus::Active,
            scopes: vec![
                "payments.read".to_string(),
                "payments.write".to_string(),
                "subscriptions.write".to_string(),
                "mandates.read".to_string(),
                "sales-invoices.read".to_string(),
                "profiles.write".to_string(),
            ],
            config: json!({}),
            metadata: json!({}),
            created_at: Utc::now(),
            updated_at: Utc::now(),
            deleted_at: None,
        };

        let capabilities = capabilities_from_connection(&connection);
        assert!(capabilities.connected);
        assert!(capabilities.features.payments);
        assert!(capabilities.features.subscriptions);
        assert!(capabilities.features.refunds);
        assert!(capabilities.features.documents);
        assert!(capabilities.features.mandates);
        assert!(capabilities.features.sales_invoices);
        assert!(capabilities.claims.contains(&"billing.read".to_string()));
        assert!(capabilities.claims.contains(&"billing.manage".to_string()));
        assert!(
            capabilities
                .claims
                .contains(&"billing.subscription.manage".to_string())
        );
        assert!(
            capabilities
                .claims
                .contains(&"billing.provider.admin".to_string())
        );
    }

    #[test]
    fn stripe_translation_is_explicit_and_deduped_globally() {
        let translated = billing_translated_rights();
        let payments_read = translated
            .iter()
            .find(|right| right.key == "billing.payments.read")
            .expect("billing.payments.read should exist");

        assert_eq!(payments_read.providers.len(), 2);
        assert!(
            payments_read
                .providers
                .iter()
                .any(|binding| binding.vendor_key == "payments.read")
        );
        assert!(
            payments_read
                .providers
                .iter()
                .any(|binding| binding.vendor_key == "payment_intents.read")
        );
    }

    #[test]
    fn scope_resolution_maps_vendor_rights_into_translated_keys() {
        let resolved = resolve_billing_translated_rights(
            BillingProvider::Stripe,
            &[
                "payment_intents.read".to_string(),
                "subscriptions.read".to_string(),
                "unknown.scope".to_string(),
            ],
        );

        assert_eq!(
            resolved,
            vec![
                "billing.payments.read".to_string(),
                "billing.subscriptions.read".to_string()
            ]
        );
    }

    #[test]
    fn inactive_connection_reports_disconnected() {
        let connection = BillingProviderConnection {
            id: Uuid::new_v4(),
            provider: BillingProvider::Stripe,
            provider_account_id: "acct_123".to_string(),
            provider_profile_id: None,
            mode: BillingConnectionMode::Test,
            status: BillingConnectionStatus::Disabled,
            scopes: vec![],
            config: json!({}),
            metadata: json!({}),
            created_at: Utc::now(),
            updated_at: Utc::now(),
            deleted_at: None,
        };

        let capabilities = capabilities_from_connection(&connection);
        assert!(!capabilities.connected);
        assert_eq!(capabilities.claims, Vec::<String>::new());
    }

    #[test]
    fn billing_grants_catalog_contains_event_rights() {
        assert!(billing_grant_by_key("payment-link.paid").is_some());
        assert!(billing_grant_by_key("invoice.paid").is_some());
        assert!(
            BILLING_GRANT_CATALOG
                .iter()
                .any(|grant| grant.key == "billing.read")
        );
    }

    #[test]
    fn billing_permission_object_groups_actions_by_resource() {
        let permissions =
            billing_permission_object_for_grants(["payment-link.paid", "payment.failed"]);

        assert_eq!(
            permissions,
            json!({
                "payment-link": ["paid"],
                "payment": ["failed"]
            })
        );
    }

    #[test]
    fn payment_status_grants_include_payment_link_variants() {
        assert_eq!(
            grant_keys_for_payment_status(AthenaPaymentStatus::Paid, true),
            vec!["payment.paid", "payment-link.paid"]
        );
        assert_eq!(
            grant_keys_for_payment_status(AthenaPaymentStatus::Failed, false),
            vec!["payment.failed"]
        );
        assert_eq!(
            grant_keys_for_payment_status(AthenaPaymentStatus::Refunded, false),
            vec!["payment.refunded"]
        );
    }

    #[test]
    fn subscription_and_invoice_status_grants_follow_canonical_state() {
        assert_eq!(
            grant_keys_for_subscription_status(AthenaSubscriptionStatus::Active),
            vec!["subscription.active"]
        );
        assert_eq!(
            grant_keys_for_subscription_status(AthenaSubscriptionStatus::PastDue),
            vec!["subscription.past-due"]
        );
        assert_eq!(
            grant_keys_for_invoice_status(AthenaInvoiceStatus::Paid),
            vec!["invoice.paid"]
        );
        assert_eq!(
            grant_keys_for_invoice_status(AthenaInvoiceStatus::Draft),
            Vec::<&'static str>::new()
        );
    }
}
