use serde::{Deserialize, Serialize};

#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BatchPartyInput {
    /// Tax identification number (NIP).
    pub nip: String,
    /// Legal or trade name of the party.
    pub name: String,
    /// Address line 1 or full free-form address.
    pub address: Option<String>,
    /// Address line 2 (typically postal code and city).
    #[serde(rename = "addressL2", default)]
    pub address_l2: Option<String>,
    /// KRS number (optional).
    #[serde(default)]
    pub krs: Option<String>,
    /// REGON number (optional).
    #[serde(default)]
    pub regon: Option<String>,
}

#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BatchLineItemInput {
    /// Line number in the invoice.
    #[serde(rename = "lineNumber")]
    pub line_number: u32,
    /// Product or service description.
    pub description: String,
    /// Unit of measure.
    pub unit: String,
    /// Quantity.
    pub quantity: f64,
    /// Unit net price.
    #[serde(rename = "unitPrice")]
    pub unit_price: f64,
    /// Net amount for the line.
    #[serde(rename = "netAmount")]
    pub net_amount: f64,
    /// VAT rate code per FA(3), e.g. "23", "8", "0 KR", "zw".
    #[serde(rename = "vatRate")]
    pub vat_rate: String,
    /// Corrected description (P_7Z) for correction invoices.
    #[serde(rename = "descriptionZ", default)]
    pub description_z: Option<String>,
    /// Corrected unit (P_8AZ).
    #[serde(rename = "unitZ", default)]
    pub unit_z: Option<String>,
    /// Corrected quantity (P_8BZ).
    #[serde(rename = "quantityZ", default)]
    pub quantity_z: Option<f64>,
    /// Corrected unit net price (P_9AZ).
    #[serde(rename = "unitPriceZ", default)]
    pub unit_price_z: Option<f64>,
    /// Corrected net amount (P_11NettoZ).
    #[serde(rename = "netAmountZ", default)]
    pub net_amount_z: Option<f64>,
    /// Corrected VAT amount (P_11VatZ).
    #[serde(rename = "vatAmountZ", default)]
    pub vat_amount_z: Option<f64>,
    /// Corrected VAT rate (P_12Z).
    #[serde(rename = "vatRateZ", default)]
    pub vat_rate_z: Option<String>,
    /// Before-correction marker (StanPrzedZ).
    #[serde(rename = "stanPrzedZ", default)]
    pub stan_przed_z: Option<u8>,
    /// Seller internal product/service code (Indeks).
    #[serde(default)]
    pub indeks: Option<String>,
    /// GTIN code.
    #[serde(default)]
    pub gtin: Option<String>,
    /// PKWiU code.
    #[serde(default)]
    pub pkwiu: Option<String>,
    /// CN code.
    #[serde(default)]
    pub cn: Option<String>,
    /// Line discount value (P_10).
    #[serde(default)]
    pub p_10: Option<f64>,
    /// Line-level service date (P_6A).
    #[serde(rename = "p6a", default)]
    pub p_6a: Option<String>,
    /// GTU classification code.
    #[serde(default)]
    pub gtu: Option<String>,
}

#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BatchPodmiot3Input {
    /// Optional NIP for Podmiot3.
    #[serde(default)]
    pub nip: Option<String>,
    /// Name of Podmiot3.
    pub name: String,
    /// Optional address of Podmiot3.
    #[serde(default)]
    pub address: Option<String>,
    /// Podmiot3 role code.
    pub role: u8,
}

#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BatchAdditionalDescription {
    /// Additional description key.
    pub key: String,
    /// Additional description value.
    pub value: String,
    /// Optional line number this description relates to.
    #[serde(
        rename = "lineNumber",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub line_number: Option<u32>,
}

fn default_currency() -> String {
    "PLN".to_string()
}

#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BatchInvoicePayload {
    /// Seller data (Podmiot1).
    pub seller: BatchPartyInput,
    /// Buyer data (Podmiot2).
    pub buyer: BatchPartyInput,
    /// Invoice number (P_2).
    #[serde(rename = "invoiceNumber")]
    pub invoice_number: String,
    /// Issue date (P_1).
    #[serde(rename = "invoiceDate")]
    pub invoice_date: String,
    /// Invoice line items (FaWiersz).
    #[serde(rename = "lineItems")]
    pub line_items: Vec<BatchLineItemInput>,
    /// Currency code (KodWaluty).
    #[serde(default = "default_currency")]
    pub currency: String,
    /// Self-billing flag (P_17).
    #[serde(rename = "isSamofakturowanie", default)]
    pub is_samofakturowanie: bool,
    /// Cash accounting flag (P_16).
    #[serde(rename = "isCashMethod", default)]
    pub is_cash_method: bool,
    /// Reverse charge flag (P_18).
    #[serde(rename = "isReverseCharge", default)]
    pub is_reverse_charge: bool,
    /// Split payment mechanism flag (P_18A).
    #[serde(rename = "isSplitPayment", default)]
    pub is_split_payment: bool,
    /// Tax exemption flag (P_19 / P_19N path decision).
    #[serde(rename = "taxExemption", default)]
    pub tax_exemption: bool,
    /// Triangular transaction flag (P_23).
    #[serde(rename = "isTriangular", default)]
    pub is_triangular: bool,
    /// Margin procedure flag (P_PMarzy path decision).
    #[serde(rename = "isMargin", default)]
    pub is_margin: bool,
    /// Sale/service date (P_6).
    #[serde(
        rename = "saleDate",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub sale_date: Option<String>,
    /// Payment method code.
    #[serde(
        rename = "paymentMethod",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub payment_method: Option<u8>,
    /// Payment due date (TerminPlatnosci).
    #[serde(
        rename = "paymentDueDate",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub payment_due_date: Option<String>,
    /// Invoice notes/footer text.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub notes: Option<String>,
    /// Bank account number (NrRB).
    #[serde(
        rename = "bankAccount",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub bank_account: Option<String>,
    /// SWIFT/BIC code.
    #[serde(
        rename = "bankSwift",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub bank_swift: Option<String>,
    /// Bank-own account indicator (RachunekWlasnyBanku).
    #[serde(
        rename = "bankOwnAccount",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub bank_own_account: Option<u8>,
    /// Bank name (NazwaBanku).
    #[serde(
        rename = "bankName",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub bank_name: Option<String>,
    /// Bank account description (OpisRachunku).
    #[serde(
        rename = "bankDescription",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub bank_description: Option<String>,
    /// Exchange rate (KursWaluty).
    #[serde(
        rename = "exchangeRate",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub exchange_rate: Option<f64>,
    /// BDO number in footer.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub bdo: Option<String>,
    /// Buyer e-mail.
    #[serde(
        rename = "buyerEmail",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub buyer_email: Option<String>,
    /// Buyer client number (NrKlienta).
    #[serde(
        rename = "buyerClientNumber",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub buyer_client_number: Option<String>,
    /// Buyer phone.
    #[serde(
        rename = "buyerPhone",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub buyer_phone: Option<String>,
    /// Invoice kind (RodzajFaktury), e.g. VAT/KOR/ZAL/ROZ/UPR.
    #[serde(rename = "invoiceType", default)]
    pub invoice_type: Option<String>,
    /// Correction reason (PrzyczynaKorekty).
    #[serde(rename = "correctionReason", default)]
    pub correction_reason: Option<String>,
    /// Correction type (TypKorekty).
    #[serde(rename = "correctionType", default)]
    pub correction_type: Option<u8>,
    /// Corrected invoice number (NrFaKorygowanej).
    #[serde(rename = "correctedInvoiceNumber", default)]
    pub corrected_invoice_number: Option<String>,
    /// Corrected invoice issue date (DataWystFaKorygowanej).
    #[serde(rename = "correctedInvoiceDate", default)]
    pub corrected_invoice_date: Option<String>,
    /// Corrected invoice KSeF number (NrKSeFFaKorygowanej).
    #[serde(rename = "correctedKsefNumber", default)]
    pub corrected_ksef_number: Option<String>,
    /// Additional descriptions (DodatkowyOpis).
    #[serde(
        rename = "additionalDescriptions",
        default,
        skip_serializing_if = "Vec::is_empty"
    )]
    pub additional_descriptions: Vec<BatchAdditionalDescription>,
    /// Seller e-mail.
    #[serde(
        rename = "sellerEmail",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub seller_email: Option<String>,
    /// Seller phone.
    #[serde(
        rename = "sellerPhone",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub seller_phone: Option<String>,
    /// Simplified invoice flag (UPR flow).
    #[serde(rename = "isSimplified", default)]
    pub is_simplified: bool,
    /// Split payment note selector (P_42_5).
    #[serde(rename = "p425", default)]
    pub p_42_5: Option<u8>,
    /// Billing period start (P_6_Od).
    #[serde(rename = "billingPeriodFrom", default)]
    pub billing_period_from: Option<String>,
    /// Billing period end (P_6_Do).
    #[serde(rename = "billingPeriodTo", default)]
    pub billing_period_to: Option<String>,
    /// NrKSeFN value for correction context.
    #[serde(rename = "nrKsefN", default)]
    pub nr_ksef_n: Option<String>,
    /// Place of issue (P_1M).
    #[serde(
        rename = "issuePlace",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub issue_place: Option<String>,
    /// Settlement data (Rozliczenie).
    #[serde(
        rename = "settlement",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub settlement: Option<String>,
    /// Override tax amount for totals section.
    #[serde(
        rename = "taxAmount",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub tax_amount: Option<f64>,
    /// Override gross amount (P_15).
    #[serde(
        rename = "grossAmount",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub gross_amount: Option<f64>,
    /// Single order number convenience field.
    #[serde(
        rename = "orderNumber",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub order_number: Option<String>,
    /// Multiple order numbers (WarunkiTransakcji/Zamowienia).
    #[serde(rename = "orderNumbers", default, skip_serializing_if = "Vec::is_empty")]
    pub order_numbers: Vec<String>,
    /// Delivery terms (WarunkiDostawy).
    #[serde(
        rename = "deliveryTerms",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub delivery_terms: Option<String>,
    /// Payment status (Zaplacono).
    #[serde(rename = "isPaid", default, skip_serializing_if = "Option::is_none")]
    pub is_paid: Option<bool>,
    /// Date paid (DataZaplaty).
    #[serde(
        rename = "paidDate",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub paid_date: Option<String>,
    /// Delivery note numbers (WZ).
    #[serde(rename = "wz", default, skip_serializing_if = "Vec::is_empty")]
    pub wz: Vec<String>,
    /// Third-party entities (Podmiot3).
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub podmiot3: Vec<BatchPodmiot3Input>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::BTreeSet;

    fn sample_payload() -> BatchInvoicePayload {
        BatchInvoicePayload {
            seller: BatchPartyInput {
                nip: "1111111111".to_string(),
                name: "Seller".to_string(),
                address: Some("Street 1".to_string()),
                address_l2: Some("00-000 City".to_string()),
                krs: Some("0000000000".to_string()),
                regon: Some("123456789".to_string()),
            },
            buyer: BatchPartyInput {
                nip: "2222222222".to_string(),
                name: "Buyer".to_string(),
                address: Some("Street 2".to_string()),
                address_l2: Some("00-001 City".to_string()),
                krs: None,
                regon: None,
            },
            invoice_number: "FV/2026/01/001".to_string(),
            invoice_date: "2026-01-10".to_string(),
            line_items: vec![BatchLineItemInput {
                line_number: 1,
                description: "Service".to_string(),
                unit: "szt".to_string(),
                quantity: 1.0,
                unit_price: 100.0,
                net_amount: 100.0,
                vat_rate: "23".to_string(),
                description_z: Some("Service corrected".to_string()),
                unit_z: Some("szt".to_string()),
                quantity_z: Some(1.0),
                unit_price_z: Some(100.0),
                net_amount_z: Some(100.0),
                vat_amount_z: Some(23.0),
                vat_rate_z: Some("23".to_string()),
                stan_przed_z: Some(1),
                indeks: Some("SKU-1".to_string()),
                gtin: Some("0123456789012".to_string()),
                pkwiu: Some("62.01.11.0".to_string()),
                cn: Some("1234".to_string()),
                p_10: Some(0.0),
                p_6a: Some("2026-01-10".to_string()),
                gtu: Some("GTU_12".to_string()),
            }],
            currency: "PLN".to_string(),
            is_samofakturowanie: false,
            is_cash_method: false,
            is_reverse_charge: false,
            is_split_payment: false,
            tax_exemption: false,
            is_triangular: false,
            is_margin: false,
            sale_date: Some("2026-01-10".to_string()),
            payment_method: Some(6),
            payment_due_date: Some("2026-01-20".to_string()),
            notes: Some("note".to_string()),
            bank_account: Some("PL00123456789012345678901234".to_string()),
            bank_swift: Some("ABCXPLPW".to_string()),
            bank_own_account: Some(1),
            bank_name: Some("Bank".to_string()),
            bank_description: Some("Opis".to_string()),
            exchange_rate: Some(1.0),
            bdo: Some("BDO-123".to_string()),
            buyer_email: Some("buyer@example.com".to_string()),
            buyer_client_number: Some("C-1".to_string()),
            buyer_phone: Some("+48111111111".to_string()),
            invoice_type: Some("VAT".to_string()),
            correction_reason: Some("reason".to_string()),
            correction_type: Some(1),
            corrected_invoice_number: Some("FV/2025/12/999".to_string()),
            corrected_invoice_date: Some("2025-12-31".to_string()),
            corrected_ksef_number: Some("1111111111-20251231-ABCDEF0123-01".to_string()),
            additional_descriptions: vec![BatchAdditionalDescription {
                key: "A".to_string(),
                value: "B".to_string(),
                line_number: Some(1),
            }],
            seller_email: Some("seller@example.com".to_string()),
            seller_phone: Some("+48123123123".to_string()),
            is_simplified: false,
            p_42_5: Some(2),
            billing_period_from: Some("2026-01-01".to_string()),
            billing_period_to: Some("2026-01-31".to_string()),
            nr_ksef_n: Some("1".to_string()),
            issue_place: Some("Warszawa".to_string()),
            settlement: Some("ROZ-1".to_string()),
            tax_amount: Some(23.0),
            gross_amount: Some(123.0),
            order_number: Some("ORD-1".to_string()),
            order_numbers: vec!["ORD-1".to_string(), "ORD-2".to_string()],
            delivery_terms: Some("EXW".to_string()),
            is_paid: Some(true),
            paid_date: Some("2026-01-21".to_string()),
            wz: vec!["WZ-1".to_string()],
            podmiot3: vec![BatchPodmiot3Input {
                nip: Some("3333333333".to_string()),
                name: "Recipient".to_string(),
                address: Some("Street 3".to_string()),
                role: 2,
            }],
        }
    }

    #[test]
    fn batch_payload_json_keys_are_strict_and_complete() {
        let value = serde_json::to_value(sample_payload()).expect("serialize payload");
        let obj = value.as_object().expect("payload object");
        let actual: BTreeSet<String> = obj.keys().cloned().collect();

        let expected: BTreeSet<String> = [
            "seller",
            "buyer",
            "invoiceNumber",
            "invoiceDate",
            "lineItems",
            "currency",
            "isSamofakturowanie",
            "isCashMethod",
            "isReverseCharge",
            "isSplitPayment",
            "taxExemption",
            "isTriangular",
            "isMargin",
            "saleDate",
            "paymentMethod",
            "paymentDueDate",
            "notes",
            "bankAccount",
            "bankSwift",
            "bankOwnAccount",
            "bankName",
            "bankDescription",
            "exchangeRate",
            "bdo",
            "buyerEmail",
            "buyerClientNumber",
            "buyerPhone",
            "invoiceType",
            "correctionReason",
            "correctionType",
            "correctedInvoiceNumber",
            "correctedInvoiceDate",
            "correctedKsefNumber",
            "additionalDescriptions",
            "sellerEmail",
            "sellerPhone",
            "isSimplified",
            "p425",
            "billingPeriodFrom",
            "billingPeriodTo",
            "nrKsefN",
            "issuePlace",
            "settlement",
            "taxAmount",
            "grossAmount",
            "orderNumber",
            "orderNumbers",
            "deliveryTerms",
            "isPaid",
            "paidDate",
            "wz",
            "podmiot3",
        ]
        .into_iter()
        .map(|s| s.to_string())
        .collect();

        assert_eq!(actual, expected);
    }

    #[test]
    fn batch_payload_fields_have_xsd_presence_guards() {
        let xsd = include_str!(
            "../../ksef-mcp/temp/ksef-docs/faktury/schemy/FA/schemat_FA(3)_v1-0E.xsd"
        );

        let required_tokens = [
            "name=\"Podmiot1\"",
            "name=\"Podmiot2\"",
            "name=\"Podmiot3\"",
            "name=\"NIP\"",
            "name=\"Nazwa\"",
            "name=\"Adres\"",
            "name=\"AdresL1\"",
            "name=\"AdresL2\"",
            "name=\"KRS\"",
            "name=\"REGON\"",
            "name=\"P_1\"",
            "name=\"P_2\"",
            "name=\"KodWaluty\"",
            "name=\"FaWiersz\"",
            "name=\"P_7\"",
            "name=\"P_8A\"",
            "name=\"P_8B\"",
            "name=\"P_9A\"",
            "name=\"P_11\"",
            "name=\"P_12\"",
            "name=\"Indeks\"",
            "name=\"GTIN\"",
            "name=\"PKWiU\"",
            "name=\"CN\"",
            "name=\"P_10\"",
            "name=\"P_6A\"",
            "name=\"GTU\"",
            "name=\"P_7Z\"",
            "name=\"P_8AZ\"",
            "name=\"P_8BZ\"",
            "name=\"P_9AZ\"",
            "name=\"P_11NettoZ\"",
            "name=\"P_11VatZ\"",
            "name=\"P_12Z\"",
            "name=\"StanPrzedZ\"",
            "name=\"UU_ID\"",
            "name=\"P_6\"",
            "name=\"P_1M\"",
            "name=\"P_16\"",
            "name=\"P_17\"",
            "name=\"P_18\"",
            "name=\"P_18A\"",
            "name=\"P_19\"",
            "name=\"P_23\"",
            "name=\"FormaPlatnosci\"",
            "name=\"TerminPlatnosci\"",
            "name=\"NrRB\"",
            "name=\"SWIFT\"",
            "name=\"RachunekWlasnyBanku\"",
            "name=\"NazwaBanku\"",
            "name=\"OpisRachunku\"",
            "name=\"KursWaluty\"",
            "name=\"BDO\"",
            "name=\"Email\"",
            "name=\"NrKlienta\"",
            "name=\"RodzajFaktury\"",
            "name=\"PrzyczynaKorekty\"",
            "name=\"TypKorekty\"",
            "name=\"NrFaKorygowanej\"",
            "name=\"DataWystFaKorygowanej\"",
            "name=\"NrKSeFFaKorygowanej\"",
            "name=\"DodatkowyOpis\"",
            "name=\"Telefon\"",
            "name=\"P_42_5\"",
            "name=\"P_6_Od\"",
            "name=\"P_6_Do\"",
            "name=\"NrKSeFN\"",
            "name=\"P_14_1\"",
            "name=\"P_15\"",
            "name=\"Rozliczenie\"",
            "name=\"Zaplacono\"",
            "name=\"DataZaplaty\"",
            "name=\"NrZamowienia\"",
            "name=\"WarunkiDostawy\"",
            "name=\"WZ\"",
            "name=\"Rola\"",
        ];

        for token in required_tokens {
            assert!(
                xsd.contains(token),
                "Expected XSD token missing for shared payload mapping guard: {token}"
            );
        }
    }
}
