//! Wave Accounting API integration
//!
//! Provides access to Wave's GraphQL API for invoice creation,
//! customer management, and product management.

use reqwest::Client;
use serde::{Deserialize, Deserializer, Serialize};

const WAVE_API_URL: &str = "https://gql.waveapps.com/graphql/public";

/// Deserialize a value that can be either a number or a string representation of a number
fn deserialize_string_or_number<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
    D: Deserializer<'de>,
{
    use serde::de::Error;

    #[derive(Deserialize)]
    #[serde(untagged)]
    enum StringOrNumber {
        Number(f64),
        String(String),
    }

    match StringOrNumber::deserialize(deserializer)? {
        StringOrNumber::Number(n) => Ok(n),
        StringOrNumber::String(s) => s.parse().map_err(|_| D::Error::custom(format!("invalid number: {}", s))),
    }
}

/// Deserialize null as empty vec (Wave API returns null for empty arrays)
fn deserialize_null_as_empty_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
where
    D: Deserializer<'de>,
    T: Deserialize<'de>,
{
    Option::<Vec<T>>::deserialize(deserializer).map(|opt| opt.unwrap_or_default())
}

/// Wave API service
pub struct WaveService {
    client: Client,
    access_token: String,
    business_id: String,
}

impl WaveService {
    /// Create a new Wave service
    pub fn new(access_token: String, business_id: String) -> Self {
        Self {
            client: Client::new(),
            access_token,
            business_id,
        }
    }

    /// Get the business ID
    pub fn business_id(&self) -> &str {
        &self.business_id
    }

    /// Execute a GraphQL query against the Wave API
    async fn execute<T: for<'de> Deserialize<'de>>(
        &self,
        query: &str,
        variables: Option<serde_json::Value>,
    ) -> Result<T, WaveError> {
        let body = serde_json::json!({
            "query": query,
            "variables": variables.unwrap_or(serde_json::json!({}))
        });

        let response = self
            .client
            .post(WAVE_API_URL)
            .header("Authorization", format!("Bearer {}", self.access_token))
            .header("Content-Type", "application/json")
            .json(&body)
            .send()
            .await
            .map_err(|e| WaveError::Request(e.to_string()))?;

        let status = response.status();
        let text = response
            .text()
            .await
            .map_err(|e| WaveError::Request(e.to_string()))?;

        if !status.is_success() {
            return Err(WaveError::Api(format!("HTTP {}: {}", status, text)));
        }

        let result: GraphQLResponse<T> =
            serde_json::from_str(&text).map_err(|e| WaveError::Parse(e.to_string()))?;

        if let Some(errors) = result.errors {
            if !errors.is_empty() {
                return Err(WaveError::GraphQL(
                    errors.into_iter().map(|e| e.message).collect(),
                ));
            }
        }

        result
            .data
            .ok_or_else(|| WaveError::Api("No data returned".to_string()))
    }

    /// List all products for the business
    pub async fn list_products(&self) -> Result<Vec<WaveProduct>, WaveError> {
        let query = r#"
            query ListProducts($businessId: ID!) {
                business(id: $businessId) {
                    products(page: 1, pageSize: 100) {
                        edges {
                            node {
                                id
                                name
                                description
                                unitPrice
                                isSold
                                isArchived
                                defaultSalesTaxes {
                                    id
                                    name
                                    rate
                                }
                                incomeAccount {
                                    id
                                    name
                                }
                            }
                        }
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "businessId": self.business_id
        });

        let response: ListProductsResponse = self.execute(query, Some(variables)).await?;

        Ok(response
            .business
            .products
            .edges
            .into_iter()
            .map(|e| e.node)
            .collect())
    }

    /// List all customers for the business
    pub async fn list_customers(&self) -> Result<Vec<WaveCustomer>, WaveError> {
        let query = r#"
            query ListCustomers($businessId: ID!) {
                business(id: $businessId) {
                    customers(page: 1, pageSize: 100) {
                        edges {
                            node {
                                id
                                name
                                email
                                address {
                                    addressLine1
                                    addressLine2
                                    city
                                    province {
                                        code
                                        name
                                    }
                                    postalCode
                                }
                                currency {
                                    code
                                }
                            }
                        }
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "businessId": self.business_id
        });

        let response: ListCustomersResponse = self.execute(query, Some(variables)).await?;

        Ok(response
            .business
            .customers
            .edges
            .into_iter()
            .map(|e| e.node)
            .collect())
    }

    /// Get a single invoice by ID
    pub async fn get_invoice(&self, invoice_id: &str) -> Result<WaveInvoice, WaveError> {
        let query = r#"
            query GetInvoice($businessId: ID!, $invoiceId: ID!) {
                business(id: $businessId) {
                    invoice(id: $invoiceId) {
                        id
                        invoiceNumber
                        invoiceDate
                        dueDate
                        status
                        customer {
                            id
                            name
                        }
                        items {
                            description
                            quantity
                            unitPrice
                            total {
                                value
                                currency {
                                    code
                                    symbol
                                }
                            }
                        }
                        subtotal {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        total {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        amountDue {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        amountPaid {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        pdfUrl
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "businessId": self.business_id,
            "invoiceId": invoice_id
        });

        let response: GetInvoiceResponse = self.execute(query, Some(variables)).await?;

        response
            .business
            .invoice
            .ok_or_else(|| WaveError::Api("Invoice not found".to_string()))
    }

    /// Create a new invoice in Wave
    pub async fn create_invoice(&self, input: CreateInvoiceInput) -> Result<WaveInvoice, WaveError> {
        let query = r#"
            mutation CreateInvoice($input: InvoiceCreateInput!) {
                invoiceCreate(input: $input) {
                    didSucceed
                    inputErrors {
                        code
                        message
                        path
                    }
                    invoice {
                        id
                        invoiceNumber
                        invoiceDate
                        dueDate
                        status
                        customer {
                            id
                            name
                        }
                        items {
                            description
                            quantity
                            unitPrice
                            total {
                                value
                                currency {
                                    code
                                    symbol
                                }
                            }
                        }
                        subtotal {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        total {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        amountDue {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        amountPaid {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        pdfUrl
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "input": input
        });

        let response: CreateInvoiceResponse = self.execute(query, Some(variables)).await?;

        if !response.invoice_create.did_succeed {
            let errors: Vec<String> = response
                .invoice_create
                .input_errors
                .into_iter()
                .map(|e| format!("{}: {}", e.path.join("."), e.message))
                .collect();
            return Err(WaveError::Validation(errors));
        }

        response
            .invoice_create
            .invoice
            .ok_or_else(|| WaveError::Api("No invoice returned".to_string()))
    }

    /// List invoices for the business with pagination
    pub async fn list_invoices(
        &self,
        page: i32,
        page_size: i32,
    ) -> Result<WaveInvoiceConnection, WaveError> {
        let query = r#"
            query ListInvoices($businessId: ID!, $page: Int!, $pageSize: Int!) {
                business(id: $businessId) {
                    invoices(page: $page, pageSize: $pageSize, sort: [INVOICE_DATE_DESC]) {
                        pageInfo {
                            currentPage
                            totalPages
                            totalCount
                        }
                        edges {
                            node {
                                id
                                invoiceNumber
                                invoiceDate
                                dueDate
                                status
                                customer {
                                    id
                                    name
                                }
                                subtotal {
                                    value
                                    currency {
                                        code
                                        symbol
                                    }
                                }
                                total {
                                    value
                                    currency {
                                        code
                                        symbol
                                    }
                                }
                                amountDue {
                                    value
                                    currency {
                                        code
                                        symbol
                                    }
                                }
                                amountPaid {
                                    value
                                    currency {
                                        code
                                        symbol
                                    }
                                }
                            }
                        }
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "businessId": self.business_id,
            "page": page,
            "pageSize": page_size
        });

        let response: ListInvoicesResponse = self.execute(query, Some(variables)).await?;

        let invoices = response.business.invoices;
        Ok(WaveInvoiceConnection {
            page_info: invoices.page_info,
            invoices: invoices.edges.into_iter().map(|e| e.node).collect(),
        })
    }

    /// Approve a draft invoice (moves to SAVED status - point of no return for edits)
    pub async fn approve_invoice(&self, invoice_id: &str) -> Result<WaveInvoice, WaveError> {
        let query = r#"
            mutation ApproveInvoice($input: InvoiceApproveInput!) {
                invoiceApprove(input: $input) {
                    didSucceed
                    inputErrors {
                        code
                        message
                        path
                    }
                    invoice {
                        id
                        invoiceNumber
                        invoiceDate
                        dueDate
                        status
                        customer {
                            id
                            name
                        }
                        items {
                            description
                            quantity
                            unitPrice
                            total {
                                value
                                currency {
                                    code
                                    symbol
                                }
                            }
                        }
                        subtotal {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        total {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        amountDue {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        amountPaid {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        pdfUrl
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "input": {
                "invoiceId": invoice_id
            }
        });

        let response: ApproveInvoiceResponse = self.execute(query, Some(variables)).await?;

        if !response.invoice_approve.did_succeed {
            let errors: Vec<String> = response
                .invoice_approve
                .input_errors
                .into_iter()
                .map(|e| format!("{}: {}", e.path.join("."), e.message))
                .collect();
            return Err(WaveError::Validation(errors));
        }

        response
            .invoice_approve
            .invoice
            .ok_or_else(|| WaveError::Api("No invoice returned".to_string()))
    }

    /// Send an approved invoice to the customer
    pub async fn send_invoice(&self, invoice_id: &str) -> Result<WaveInvoice, WaveError> {
        let query = r#"
            mutation SendInvoice($input: InvoiceSendInput!) {
                invoiceSend(input: $input) {
                    didSucceed
                    inputErrors {
                        code
                        message
                        path
                    }
                    invoice {
                        id
                        invoiceNumber
                        invoiceDate
                        dueDate
                        status
                        customer {
                            id
                            name
                        }
                        items {
                            description
                            quantity
                            unitPrice
                            total {
                                value
                                currency {
                                    code
                                    symbol
                                }
                            }
                        }
                        subtotal {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        total {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        amountDue {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        amountPaid {
                            value
                            currency {
                                code
                                symbol
                            }
                        }
                        pdfUrl
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "input": {
                "invoiceId": invoice_id
            }
        });

        let response: SendInvoiceResponse = self.execute(query, Some(variables)).await?;

        if !response.invoice_send.did_succeed {
            let errors: Vec<String> = response
                .invoice_send
                .input_errors
                .into_iter()
                .map(|e| format!("{}: {}", e.path.join("."), e.message))
                .collect();
            return Err(WaveError::Validation(errors));
        }

        response
            .invoice_send
            .invoice
            .ok_or_else(|| WaveError::Api("No invoice returned".to_string()))
    }

    /// Delete a draft invoice
    pub async fn delete_invoice(&self, invoice_id: &str) -> Result<bool, WaveError> {
        let query = r#"
            mutation DeleteInvoice($input: InvoiceDeleteInput!) {
                invoiceDelete(input: $input) {
                    didSucceed
                    inputErrors {
                        code
                        message
                        path
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "input": {
                "invoiceId": invoice_id
            }
        });

        let response: DeleteInvoiceResponse = self.execute(query, Some(variables)).await?;

        if !response.invoice_delete.did_succeed {
            let errors: Vec<String> = response
                .invoice_delete
                .input_errors
                .into_iter()
                .map(|e| format!("{}: {}", e.path.join("."), e.message))
                .collect();
            return Err(WaveError::Validation(errors));
        }

        Ok(true)
    }

    /// Get a single customer by ID
    pub async fn get_customer(&self, customer_id: &str) -> Result<WaveCustomer, WaveError> {
        let query = r#"
            query GetCustomer($businessId: ID!, $customerId: ID!) {
                business(id: $businessId) {
                    customer(id: $customerId) {
                        id
                        name
                        email
                        address {
                            addressLine1
                            addressLine2
                            city
                            province {
                                code
                                name
                            }
                            postalCode
                        }
                        currency {
                            code
                        }
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "businessId": self.business_id,
            "customerId": customer_id
        });

        let response: GetCustomerResponse = self.execute(query, Some(variables)).await?;

        response
            .business
            .customer
            .ok_or_else(|| WaveError::Api("Customer not found".to_string()))
    }

    /// Get a single product by ID
    pub async fn get_product(&self, product_id: &str) -> Result<WaveProduct, WaveError> {
        let query = r#"
            query GetProduct($businessId: ID!, $productId: ID!) {
                business(id: $businessId) {
                    product(id: $productId) {
                        id
                        name
                        description
                        unitPrice
                        isSold
                        isArchived
                        defaultSalesTaxes {
                            id
                            name
                            rate
                        }
                        incomeAccount {
                            id
                            name
                        }
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "businessId": self.business_id,
            "productId": product_id
        });

        let response: GetProductResponse = self.execute(query, Some(variables)).await?;

        response
            .business
            .product
            .ok_or_else(|| WaveError::Api("Product not found".to_string()))
    }

    /// Create a new product in Wave
    pub async fn create_product(&self, input: CreateProductInput) -> Result<WaveProduct, WaveError> {
        let query = r#"
            mutation CreateProduct($input: ProductCreateInput!) {
                productCreate(input: $input) {
                    didSucceed
                    inputErrors {
                        code
                        message
                        path
                    }
                    product {
                        id
                        name
                        description
                        unitPrice
                        isSold
                        isArchived
                        defaultSalesTaxes {
                            id
                            name
                            rate
                        }
                        incomeAccount {
                            id
                            name
                        }
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "input": input
        });

        let response: CreateProductResponse = self.execute(query, Some(variables)).await?;

        if !response.product_create.did_succeed {
            let errors: Vec<String> = response
                .product_create
                .input_errors
                .into_iter()
                .map(|e| format!("{}: {}", e.path.join("."), e.message))
                .collect();
            return Err(WaveError::Validation(errors));
        }

        response
            .product_create
            .product
            .ok_or_else(|| WaveError::Api("No product returned".to_string()))
    }

    /// Update an existing product in Wave
    pub async fn update_product(&self, input: UpdateProductInput) -> Result<WaveProduct, WaveError> {
        let query = r#"
            mutation UpdateProduct($input: ProductPatchInput!) {
                productPatch(input: $input) {
                    didSucceed
                    inputErrors {
                        code
                        message
                        path
                    }
                    product {
                        id
                        name
                        description
                        unitPrice
                        isSold
                        isArchived
                        defaultSalesTaxes {
                            id
                            name
                            rate
                        }
                        incomeAccount {
                            id
                            name
                        }
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "input": input
        });

        let response: UpdateProductResponse = self.execute(query, Some(variables)).await?;

        if !response.product_patch.did_succeed {
            let errors: Vec<String> = response
                .product_patch
                .input_errors
                .into_iter()
                .map(|e| format!("{}: {}", e.path.join("."), e.message))
                .collect();
            return Err(WaveError::Validation(errors));
        }

        response
            .product_patch
            .product
            .ok_or_else(|| WaveError::Api("No product returned".to_string()))
    }

    /// Archive a product in Wave
    pub async fn archive_product(&self, product_id: &str) -> Result<WaveProduct, WaveError> {
        let query = r#"
            mutation ArchiveProduct($input: ProductArchiveInput!) {
                productArchive(input: $input) {
                    didSucceed
                    inputErrors {
                        code
                        message
                        path
                    }
                    product {
                        id
                        name
                        description
                        unitPrice
                        isSold
                        isArchived
                        defaultSalesTaxes {
                            id
                            name
                            rate
                        }
                        incomeAccount {
                            id
                            name
                        }
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "input": {
                "id": product_id
            }
        });

        let response: ArchiveProductResponse = self.execute(query, Some(variables)).await?;

        if !response.product_archive.did_succeed {
            let errors: Vec<String> = response
                .product_archive
                .input_errors
                .into_iter()
                .map(|e| format!("{}: {}", e.path.join("."), e.message))
                .collect();
            return Err(WaveError::Validation(errors));
        }

        response
            .product_archive
            .product
            .ok_or_else(|| WaveError::Api("No product returned".to_string()))
    }

    /// Update an existing customer in Wave
    pub async fn update_customer(&self, input: UpdateCustomerInput) -> Result<WaveCustomer, WaveError> {
        let query = r#"
            mutation UpdateCustomer($input: CustomerPatchInput!) {
                customerPatch(input: $input) {
                    didSucceed
                    inputErrors {
                        code
                        message
                        path
                    }
                    customer {
                        id
                        name
                        email
                        address {
                            addressLine1
                            addressLine2
                            city
                            province {
                                code
                                name
                            }
                            postalCode
                        }
                        currency {
                            code
                        }
                    }
                }
            }
        "#;

        // Serialize input and encode the ID to Relay Global ID format
        let mut json_input = serde_json::to_value(&input).map_err(|e| WaveError::Parse(e.to_string()))?;
        if let serde_json::Value::Object(ref mut map) = json_input {
            map.insert("id".to_string(), serde_json::json!(&input.id));
        }

        let variables = serde_json::json!({
            "input": json_input
        });

        let response: UpdateCustomerResponse = self.execute(query, Some(variables)).await?;

        if !response.customer_patch.did_succeed {
            let errors: Vec<String> = response
                .customer_patch
                .input_errors
                .into_iter()
                .map(|e| format!("{}: {}", e.path.join("."), e.message))
                .collect();
            return Err(WaveError::Validation(errors));
        }

        response
            .customer_patch
            .customer
            .ok_or_else(|| WaveError::Api("No customer returned".to_string()))
    }

    /// Delete a customer in Wave
    pub async fn delete_customer(&self, customer_id: &str) -> Result<bool, WaveError> {
        let query = r#"
            mutation DeleteCustomer($input: CustomerDeleteInput!) {
                customerDelete(input: $input) {
                    didSucceed
                    inputErrors {
                        code
                        message
                        path
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "input": {
                "id": customer_id
            }
        });

        let response: DeleteCustomerResponse = self.execute(query, Some(variables)).await?;

        if !response.customer_delete.did_succeed {
            let errors: Vec<String> = response
                .customer_delete
                .input_errors
                .into_iter()
                .map(|e| format!("{}: {}", e.path.join("."), e.message))
                .collect();
            return Err(WaveError::Validation(errors));
        }

        Ok(true)
    }

    /// Create a new customer in Wave
    pub async fn create_customer(&self, input: CreateCustomerInput) -> Result<WaveCustomer, WaveError> {
        let query = r#"
            mutation CreateCustomer($input: CustomerCreateInput!) {
                customerCreate(input: $input) {
                    didSucceed
                    inputErrors {
                        code
                        message
                        path
                    }
                    customer {
                        id
                        name
                        email
                        address {
                            addressLine1
                            addressLine2
                            city
                            province {
                                code
                                name
                            }
                            postalCode
                        }
                        currency {
                            code
                        }
                    }
                }
            }
        "#;

        let variables = serde_json::json!({
            "input": input
        });

        let response: CreateCustomerResponse = self.execute(query, Some(variables)).await?;

        if !response.customer_create.did_succeed {
            let errors: Vec<String> = response
                .customer_create
                .input_errors
                .into_iter()
                .map(|e| format!("{}: {}", e.path.join("."), e.message))
                .collect();
            return Err(WaveError::Validation(errors));
        }

        response
            .customer_create
            .customer
            .ok_or_else(|| WaveError::Api("No customer returned".to_string()))
    }
}

// ============================================================================
// Error types
// ============================================================================

#[derive(Debug, thiserror::Error)]
pub enum WaveError {
    #[error("Request error: {0}")]
    Request(String),

    #[error("API error: {0}")]
    Api(String),

    #[error("Parse error: {0}")]
    Parse(String),

    #[error("GraphQL errors: {0:?}")]
    GraphQL(Vec<String>),

    #[error("Validation errors: {0:?}")]
    Validation(Vec<String>),
}

// ============================================================================
// GraphQL response types
// ============================================================================

#[derive(Debug, Deserialize)]
struct GraphQLResponse<T> {
    data: Option<T>,
    errors: Option<Vec<GraphQLError>>,
}

#[derive(Debug, Deserialize)]
struct GraphQLError {
    message: String,
}

// ============================================================================
// Wave API response types
// ============================================================================

#[derive(Debug, Deserialize)]
struct ListProductsResponse {
    business: BusinessProducts,
}

#[derive(Debug, Deserialize)]
struct BusinessProducts {
    products: Connection<WaveProduct>,
}

#[derive(Debug, Deserialize)]
struct ListCustomersResponse {
    business: BusinessCustomers,
}

#[derive(Debug, Deserialize)]
struct BusinessCustomers {
    customers: Connection<WaveCustomer>,
}

#[derive(Debug, Deserialize)]
struct GetInvoiceResponse {
    business: BusinessInvoice,
}

#[derive(Debug, Deserialize)]
struct BusinessInvoice {
    invoice: Option<WaveInvoice>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreateInvoiceResponse {
    invoice_create: InvoiceCreateResult,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct InvoiceCreateResult {
    did_succeed: bool,
    #[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
    input_errors: Vec<InputError>,
    invoice: Option<WaveInvoice>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreateCustomerResponse {
    customer_create: CustomerCreateResult,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CustomerCreateResult {
    did_succeed: bool,
    #[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
    input_errors: Vec<InputError>,
    customer: Option<WaveCustomer>,
}

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct InputError {
    code: String,
    message: String,
    #[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
    path: Vec<String>,
}

#[derive(Debug, Deserialize)]
struct Connection<T> {
    edges: Vec<Edge<T>>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct PagedConnection<T> {
    page_info: WavePageInfo,
    edges: Vec<Edge<T>>,
}

#[derive(Debug, Deserialize)]
struct Edge<T> {
    node: T,
}

#[derive(Debug, Deserialize)]
struct ListInvoicesResponse {
    business: BusinessInvoices,
}

#[derive(Debug, Deserialize)]
struct BusinessInvoices {
    invoices: PagedConnection<WaveInvoiceListItem>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ApproveInvoiceResponse {
    invoice_approve: InvoiceMutationResult,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SendInvoiceResponse {
    invoice_send: InvoiceMutationResult,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DeleteInvoiceResponse {
    invoice_delete: DeleteResult,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct InvoiceMutationResult {
    did_succeed: bool,
    #[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
    input_errors: Vec<InputError>,
    invoice: Option<WaveInvoice>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DeleteResult {
    did_succeed: bool,
    #[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
    input_errors: Vec<InputError>,
}

#[derive(Debug, Deserialize)]
struct GetCustomerResponse {
    business: BusinessCustomer,
}

#[derive(Debug, Deserialize)]
struct BusinessCustomer {
    customer: Option<WaveCustomer>,
}

#[derive(Debug, Deserialize)]
struct GetProductResponse {
    business: BusinessProduct,
}

#[derive(Debug, Deserialize)]
struct BusinessProduct {
    product: Option<WaveProduct>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreateProductResponse {
    product_create: ProductMutationResult,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct UpdateProductResponse {
    product_patch: ProductMutationResult,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ArchiveProductResponse {
    product_archive: ProductMutationResult,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ProductMutationResult {
    did_succeed: bool,
    #[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
    input_errors: Vec<InputError>,
    product: Option<WaveProduct>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct UpdateCustomerResponse {
    customer_patch: CustomerCreateResult,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DeleteCustomerResponse {
    customer_delete: DeleteResult,
}

// ============================================================================
// Wave data types (public)
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WaveProduct {
    pub id: String,
    pub name: String,
    pub description: Option<String>,
    #[serde(deserialize_with = "deserialize_string_or_number")]
    pub unit_price: f64,
    pub is_sold: bool,
    pub is_archived: bool,
    #[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
    pub default_sales_taxes: Vec<WaveTax>,
    pub income_account: Option<WaveAccount>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WaveTax {
    pub id: String,
    pub name: String,
    #[serde(deserialize_with = "deserialize_string_or_number")]
    pub rate: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WaveAccount {
    pub id: String,
    pub name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WaveCustomer {
    pub id: String,
    pub name: String,
    pub email: Option<String>,
    pub address: Option<WaveAddress>,
    pub currency: Option<WaveCurrency>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WaveAddress {
    pub address_line1: Option<String>,
    pub address_line2: Option<String>,
    pub city: Option<String>,
    pub province: Option<WaveProvince>,
    pub postal_code: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WaveProvince {
    pub code: String,
    pub name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WaveCurrency {
    pub code: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WaveInvoice {
    pub id: String,
    pub invoice_number: String,
    pub invoice_date: String,
    pub due_date: Option<String>,
    pub status: String,
    pub customer: WaveInvoiceCustomer,
    pub items: Vec<WaveInvoiceItem>,
    pub subtotal: WaveMoney,
    pub total: WaveMoney,
    pub amount_due: WaveMoney,
    pub amount_paid: WaveMoney,
    pub pdf_url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WaveInvoiceCustomer {
    pub id: String,
    pub name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WaveInvoiceItem {
    pub description: String,
    #[serde(deserialize_with = "deserialize_string_or_number")]
    pub quantity: f64,
    #[serde(deserialize_with = "deserialize_string_or_number")]
    pub unit_price: f64,
    pub total: WaveMoney,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WaveMoney {
    pub value: String,
    pub currency: WaveMoneyurrency,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WaveMoneyurrency {
    pub code: String,
    pub symbol: String,
}

// ============================================================================
// Input types (for mutations)
// ============================================================================

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateInvoiceInput {
    pub business_id: String,
    pub customer_id: String,
    pub items: Vec<CreateInvoiceItemInput>,
    pub invoice_date: String,
    pub due_date: Option<String>,
    pub memo: Option<String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateInvoiceItemInput {
    pub product_id: String,
    pub description: Option<String>,
    pub quantity: f64,
    pub unit_price: f64,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateCustomerInput {
    pub business_id: String,
    pub name: String,
    pub email: Option<String>,
    pub address: Option<CreateAddressInput>,
    pub currency: Option<String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateAddressInput {
    pub address_line1: Option<String>,
    pub address_line2: Option<String>,
    pub city: Option<String>,
    pub province_code: Option<String>,
    pub postal_code: Option<String>,
    pub country_code: Option<String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateProductInput {
    pub business_id: String,
    pub name: String,
    pub description: Option<String>,
    pub unit_price: f64,
    pub is_sold: bool,
    pub income_account_id: Option<String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateProductInput {
    pub id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub unit_price: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub income_account_id: Option<String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateCustomerInput {
    pub id: String,
    pub name: Option<String>,
    pub email: Option<String>,
    pub address: Option<CreateAddressInput>,
    pub currency: Option<String>,
}

// ============================================================================
// Pagination types (public)
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WavePageInfo {
    pub current_page: i32,
    pub total_pages: i32,
    pub total_count: i32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WaveInvoiceConnection {
    pub page_info: WavePageInfo,
    pub invoices: Vec<WaveInvoiceListItem>,
}

/// Lighter invoice type for list views (no items array)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WaveInvoiceListItem {
    pub id: String,
    pub invoice_number: String,
    pub invoice_date: String,
    pub due_date: Option<String>,
    pub status: String,
    pub customer: WaveInvoiceCustomer,
    pub subtotal: WaveMoney,
    pub total: WaveMoney,
    pub amount_due: WaveMoney,
    pub amount_paid: WaveMoney,
}
