import * as plugins from '../plugins.js';

/**
 * A class to convert a given ILetter with invoice data
 * into a Factur-X compliant XML (also compatible with ZUGFeRD and EN16931).
 * 
 * Factur-X is the French implementation of the European e-invoicing standard EN16931,
 * which is also implemented in Germany as ZUGFeRD. Both formats are based on
 * UN/CEFACT Cross Industry Invoice (CII) XML schemas.
 */
export class FacturXEncoder {

  constructor() {}
  
  /**
   * Alias for createFacturXXml to maintain backward compatibility
   */
  public createZugferdXml(letterArg: plugins.tsclass.business.ILetter): string {
    return this.createFacturXXml(letterArg);
  }

  /**
   * Creates a Factur-X compliant XML based on the provided letter data.
   * This XML is also compliant with ZUGFeRD and EN16931 standards.
   */
  public createFacturXXml(letterArg: plugins.tsclass.business.ILetter): string {
    // 1) Get your "SmartXml" or "xmlbuilder2" instance
    const smartxmlInstance = new plugins.smartxml.SmartXml();

    if (!letterArg?.content?.invoiceData) {
      throw new Error('Letter does not contain invoice data.');
    }

    const invoice: plugins.tsclass.finance.IInvoice = letterArg.content.invoiceData;
    const billedBy: plugins.tsclass.business.IContact = invoice.billedBy;
    const billedTo: plugins.tsclass.business.IContact = invoice.billedTo;

    // 2) Start building the document
    const doc = smartxmlInstance
      .create({ version: '1.0', encoding: 'UTF-8' })
      .ele('rsm:CrossIndustryInvoice', {
        'xmlns:rsm': 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100',
        'xmlns:udt': 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100',
        'xmlns:qdt': 'urn:un:unece:uncefact:data:standard:QualifiedDataType:100',
        'xmlns:ram': 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'
      });

    // 3) Exchanged Document Context
    const docContext = doc.ele('rsm:ExchangedDocumentContext');
    
    // Add test indicator
    docContext.ele('ram:TestIndicator')
      .ele('udt:Indicator')
        .txt(this.isDraft(letterArg) ? 'true' : 'false')
      .up()
    .up();
    
    // Add Factur-X profile information
    // EN16931 profile is compliant with both Factur-X and ZUGFeRD
    docContext.ele('ram:GuidelineSpecifiedDocumentContextParameter')
      .ele('ram:ID')
        .txt('urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:en16931')
      .up()
    .up();
    
    docContext.up(); // </rsm:ExchangedDocumentContext>

    // 4) Exchanged Document (Invoice Header Info)
    const exchangedDoc = doc.ele('rsm:ExchangedDocument');
    
    // Invoice ID
    exchangedDoc.ele('ram:ID').txt(invoice.id).up();
    
    // Document type code
    // 380 = commercial invoice, 381 = credit note
    const documentTypeCode = invoice.type === 'creditnote' ? '381' : '380';
    exchangedDoc.ele('ram:TypeCode').txt(documentTypeCode).up();
    
    // Issue date
    exchangedDoc
      .ele('ram:IssueDateTime')
        .ele('udt:DateTimeString', { format: '102' })
          // Format 'YYYYMMDD' as per Factur-X specification
          .txt(this.formatDate(letterArg.date))
        .up()
      .up();
      
    // Document name - Factur-X recommended field
    const documentName = invoice.type === 'creditnote' ? 'CREDIT NOTE' : 'INVOICE';
    exchangedDoc.ele('ram:Name').txt(documentName).up();
    
    // Optional: Add language indicator (recommended for Factur-X)
    // Use document language if specified, default to 'en'
    const languageCode = letterArg.language?.toUpperCase() || 'EN';
    exchangedDoc
      .ele('ram:IncludedNote')
        .ele('ram:Content').txt('Invoice created with Factur-X compliant software').up()
        .ele('ram:SubjectCode').txt('REG').up() // REG = regulatory information
      .up();
      
    exchangedDoc.up(); // </rsm:ExchangedDocument>

    // 5) Supply Chain Trade Transaction
    const supplyChainEle = doc.ele('rsm:SupplyChainTradeTransaction');

    // 5.1) Included Supply Chain Trade Line Items
    invoice.items.forEach((item) => {
      const lineItemEle = supplyChainEle.ele('ram:IncludedSupplyChainTradeLineItem');

      lineItemEle.ele('ram:SpecifiedTradeProduct')
        .ele('ram:Name')
        .txt(item.name)
        .up()
      .up(); // </ram:SpecifiedTradeProduct>

      lineItemEle.ele('ram:SpecifiedLineTradeAgreement')
        .ele('ram:GrossPriceProductTradePrice')
          .ele('ram:ChargeAmount')
          .txt(item.unitNetPrice.toFixed(2))
          .up()
        .up()
      .up(); // </ram:SpecifiedLineTradeAgreement>

      lineItemEle.ele('ram:SpecifiedLineTradeDelivery')
        .ele('ram:BilledQuantity')
        .txt(item.unitQuantity.toString())
        .up()
      .up(); // </ram:SpecifiedLineTradeDelivery>

      lineItemEle.ele('ram:SpecifiedLineTradeSettlement')
        .ele('ram:ApplicableTradeTax')
          .ele('ram:RateApplicablePercent')
          .txt(item.vatPercentage.toFixed(2))
          .up()
        .up()
        .ele('ram:SpecifiedTradeSettlementLineMonetarySummation')
          .ele('ram:LineTotalAmount')
          .txt(
            (
              item.unitQuantity *
              item.unitNetPrice *
              (1 + item.vatPercentage / 100)
            ).toFixed(2)
          )
          .up()
        .up()
      .up(); // </ram:SpecifiedLineTradeSettlement>
    });

    // 5.2) Applicable Header Trade Agreement
    const headerTradeAgreementEle = supplyChainEle.ele('ram:ApplicableHeaderTradeAgreement');
    // Seller
    const sellerPartyEle = headerTradeAgreementEle.ele('ram:SellerTradeParty');
    sellerPartyEle.ele('ram:Name').txt(billedBy.name).up();
    // Example: If it's a company, put company name, etc.
    const sellerAddressEle = sellerPartyEle.ele('ram:PostalTradeAddress');
    sellerAddressEle.ele('ram:PostcodeCode').txt(billedBy.address.postalCode).up();
    sellerAddressEle.ele('ram:LineOne').txt(billedBy.address.streetName).up();
    sellerAddressEle.ele('ram:CityName').txt(billedBy.address.city).up();
    // Typically you'd include 'ram:CountryID' with ISO2 code, e.g. "DE"
    sellerAddressEle.up(); // </ram:PostalTradeAddress>
    sellerPartyEle.up(); // </ram:SellerTradeParty>

    // Buyer
    const buyerPartyEle = headerTradeAgreementEle.ele('ram:BuyerTradeParty');
    buyerPartyEle.ele('ram:Name').txt(billedTo.name).up();
    const buyerAddressEle = buyerPartyEle.ele('ram:PostalTradeAddress');
    buyerAddressEle.ele('ram:PostcodeCode').txt(billedTo.address.postalCode).up();
    buyerAddressEle.ele('ram:LineOne').txt(billedTo.address.streetName).up();
    buyerAddressEle.ele('ram:CityName').txt(billedTo.address.city).up();
    buyerAddressEle.up(); // </ram:PostalTradeAddress>
    buyerPartyEle.up(); // </ram:BuyerTradeParty>
    headerTradeAgreementEle.up(); // </ram:ApplicableHeaderTradeAgreement>

    // 5.3) Applicable Header Trade Delivery
    const headerTradeDeliveryEle = supplyChainEle.ele('ram:ApplicableHeaderTradeDelivery');
    const actualDeliveryEle = headerTradeDeliveryEle.ele('ram:ActualDeliverySupplyChainEvent');
    const occurrenceEle = actualDeliveryEle.ele('ram:OccurrenceDateTime')
      .ele('udt:DateTimeString', { format: '102' });

    const deliveryDate = invoice.deliveryDate || letterArg.date;
    occurrenceEle.txt(this.formatDate(deliveryDate)).up();
    actualDeliveryEle.up(); // </ram:ActualDeliverySupplyChainEvent>
    headerTradeDeliveryEle.up(); // </ram:ApplicableHeaderTradeDelivery>

    // 5.4) Applicable Header Trade Settlement
    const headerTradeSettlementEle = supplyChainEle.ele('ram:ApplicableHeaderTradeSettlement');
    // Tax currency code, doc currency code, etc.
    headerTradeSettlementEle.ele('ram:InvoiceCurrencyCode').txt(invoice.currency).up();

    // Example single tax breakdown
    const tradeTaxEle = headerTradeSettlementEle.ele('ram:ApplicableTradeTax');
    tradeTaxEle.ele('ram:TypeCode').txt('VAT').up();
    tradeTaxEle.ele('ram:CalculatedAmount').txt(this.sumAllVat(invoice).toFixed(2)).up();
    tradeTaxEle
      .ele('ram:RateApplicablePercent')
      .txt(this.extractMainVatRate(invoice.items).toFixed(2))
      .up();
    tradeTaxEle.up(); // </ram:ApplicableTradeTax>

    // Payment Terms
    const paymentTermsEle = headerTradeSettlementEle.ele('ram:SpecifiedTradePaymentTerms');
    
    // Payment description
    paymentTermsEle.ele('ram:Description').txt(`Payment due in ${invoice.dueInDays} days.`).up();
    
    // Due date calculation
    const dueDate = new Date(letterArg.date);
    dueDate.setDate(dueDate.getDate() + invoice.dueInDays);
    
    // Add due date as per Factur-X spec
    paymentTermsEle
      .ele('ram:DueDateDateTime')
        .ele('udt:DateTimeString', { format: '102' })
          .txt(this.formatDate(dueDate.getTime()))
        .up()
      .up();
      
    // Add payment means if available
    if (invoice.billedBy.sepaConnection) {
      // Add SEPA information as per Factur-X standard
      const paymentMeans = headerTradeSettlementEle.ele('ram:SpecifiedTradeSettlementPaymentMeans');
      paymentMeans.ele('ram:TypeCode').txt('58').up(); // 58 = SEPA credit transfer
      
      // Payment reference (for bank statement reconciliation)
      paymentMeans.ele('ram:Information').txt(`Reference: ${invoice.id}`).up();
      
      // Payee account (IBAN)
      if (invoice.billedBy.sepaConnection.iban) {
        const payeeAccount = paymentMeans.ele('ram:PayeePartyCreditorFinancialAccount');
        payeeAccount.ele('ram:IBANID').txt(invoice.billedBy.sepaConnection.iban).up();
        payeeAccount.up();
      }
      
      // Bank BIC
      if (invoice.billedBy.sepaConnection.bic) {
        const payeeBank = paymentMeans.ele('ram:PayeeSpecifiedCreditorFinancialInstitution');
        payeeBank.ele('ram:BICID').txt(invoice.billedBy.sepaConnection.bic).up();
        payeeBank.up();
      }
      
      paymentMeans.up();
    }
      
    paymentTermsEle.up(); // </ram:SpecifiedTradePaymentTerms>

    // Monetary Summation
    const monetarySummationEle = headerTradeSettlementEle.ele('ram:SpecifiedTradeSettlementHeaderMonetarySummation');
    monetarySummationEle
      .ele('ram:LineTotalAmount')
      .txt(this.calcLineTotalNet(invoice).toFixed(2))
      .up();
    monetarySummationEle
      .ele('ram:TaxTotalAmount')
      .txt(this.sumAllVat(invoice).toFixed(2))
      .up();
    monetarySummationEle
      .ele('ram:GrandTotalAmount')
      .txt(this.calcGrandTotal(invoice).toFixed(2))
      .up();
    monetarySummationEle.up(); // </ram:SpecifiedTradeSettlementHeaderMonetarySummation>
    headerTradeSettlementEle.up(); // </ram:ApplicableHeaderTradeSettlement>

    supplyChainEle.up(); // </rsm:SupplyChainTradeTransaction>
    doc.up(); // </rsm:CrossIndustryInvoice>

    // 6) Return the final XML string
    return doc.end({ prettyPrint: true });
  }

  /**
   * Helper: Determine if the letter is in draft or final.
   */
  private isDraft(letterArg: plugins.tsclass.business.ILetter): boolean {
    return letterArg.versionInfo?.type === 'draft';
  }

  /**
   * Helper: Format date to certain patterns (very minimal example).
   * e.g. 'yyyyMMdd' => '20231231'
   */
  private formatDate(timestampMs: number): string {
    const date = new Date(timestampMs);
    const yyyy = date.getFullYear();
    const mm = String(date.getMonth() + 1).padStart(2, '0');
    const dd = String(date.getDate()).padStart(2, '0');
    return `${yyyy}${mm}${dd}`;
  }

  /**
   * Helper: Map your custom 'unitType' to an ISO code or similar.
   */
  private mapUnitType(unitType: string): string {
    switch (unitType.toLowerCase()) {
      case 'hour':
        return 'HUR';
      case 'piece':
        return 'C62';
      default:
        return 'C62'; // fallback
    }
  }

  /**
   * Example: Sum all VAT amounts from items.
   */
  private sumAllVat(invoice: plugins.tsclass.finance.IInvoice): number {
    return invoice.items.reduce((acc, item) => {
      const net = item.unitNetPrice * item.unitQuantity;
      const vat = net * (item.vatPercentage / 100);
      return acc + vat;
    }, 0);
  }

  /**
   * Example: Extract main (or highest) VAT rate from items as representative.
   * In reality, you might list multiple 'ApplicableTradeTax' blocks by group.
   */
  private extractMainVatRate(items: plugins.tsclass.finance.IInvoiceItem[]): number {
    let max = 0;
    items.forEach((item) => {
      if (item.vatPercentage > max) max = item.vatPercentage;
    });
    return max;
  }

  /**
   * Example: Sum net amounts (without VAT).
   */
  private calcLineTotalNet(invoice: plugins.tsclass.finance.IInvoice): number {
    return invoice.items.reduce((acc, item) => {
      const net = item.unitNetPrice * item.unitQuantity;
      return acc + net;
    }, 0);
  }

  /**
   * Example: net + VAT = grand total
   */
  private calcGrandTotal(invoice: plugins.tsclass.finance.IInvoice): number {
    const net = this.calcLineTotalNet(invoice);
    const vat = this.sumAllVat(invoice);
    return net + vat;
  }
}