import { UBLBaseDecoder } from '../ubl.decoder.js';
import type { TInvoice, TCreditNote, TDebitNote } from '../../../interfaces/common.js';
import { business, finance } from '../../../plugins.js';
import { UBLDocumentType } from '../ubl.types.js';
import { EN16931Validator } from '../../validation/en16931.validator.js';

/**
 * Decoder for XRechnung (UBL) format
 * Implements decoding of XRechnung invoices to TInvoice
 */
export class XRechnungDecoder extends UBLBaseDecoder {
  /**
   * Decodes a UBL credit note
   * @returns Promise resolving to a TCreditNote object
   */
  protected async decodeCreditNote(): Promise<TCreditNote> {
    // Extract common data
    const commonData = await this.extractCommonData();

    // Return the invoice data as a credit note
    return {
      ...commonData,
      accountingDocType: 'creditnote' as const
    } as unknown as TCreditNote;
  }

  /**
   * Decodes a UBL debit note (invoice)
   * @returns Promise resolving to a TDebitNote object
   */
  protected async decodeDebitNote(): Promise<TDebitNote> {
    // Extract common data
    const commonData = await this.extractCommonData();

    // Return the invoice data as a debit note
    return {
      ...commonData,
      accountingDocType: 'debitnote' as const
    } as unknown as TDebitNote;
  }

  /**
   * Extracts common invoice data from XRechnung XML
   * @returns Common invoice data
   */
  private async extractCommonData(): Promise<Partial<TInvoice>> {
    try {
      // Default values
      const invoiceId = this.getText('//cbc:ID', this.doc) || `INV-${Date.now()}`;
      const issueDateText = this.getText('//cbc:IssueDate', this.doc);
      const issueDate = issueDateText ? new Date(issueDateText).getTime() : Date.now();
      const currencyCode = this.getText('//cbc:DocumentCurrencyCode', this.doc) || 'EUR';

      // Extract payment terms
      let dueInDays = 30; // Default
      const dueDateText = this.getText('//cac:PaymentTerms/cbc:PaymentDueDate', this.doc);
      if (dueDateText) {
        const dueDateObj = new Date(dueDateText);
        const issueDateObj = new Date(issueDate);
        const diffTime = Math.abs(dueDateObj.getTime() - issueDateObj.getTime());
        dueInDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
      }

      // Extract items
      const items: finance.TAccountingDocItem[] = [];
      const invoiceLines = this.select('//cac:InvoiceLine', this.doc);

      if (invoiceLines && Array.isArray(invoiceLines)) {
        for (let i = 0; i < invoiceLines.length; i++) {
          const line = invoiceLines[i];

          const position = i + 1;
          const name = this.getText('./cac:Item/cbc:Name', line) || `Item ${position}`;
          const description = this.getText('./cac:Item/cbc:Description', line) || '';
          const articleNumber = this.getText('./cac:Item/cac:SellersItemIdentification/cbc:ID', line) || '';
          const buyerItemID = this.getText('./cac:Item/cac:BuyersItemIdentification/cbc:ID', line) || '';
          const standardItemID = this.getText('./cac:Item/cac:StandardItemIdentification/cbc:ID', line) || '';
          const commodityClassification = this.getText('./cac:Item/cac:CommodityClassification/cbc:ItemClassificationCode', line) || '';
          const unitType = this.getText('./cbc:InvoicedQuantity/@unitCode', line) || 'EA';

          let unitQuantity = 1;
          const quantityText = this.getText('./cbc:InvoicedQuantity', line);
          if (quantityText) {
            unitQuantity = parseFloat(quantityText) || 1;
          }

          let unitNetPrice = 0;
          const priceText = this.getText('./cac:Price/cbc:PriceAmount', line);
          if (priceText) {
            unitNetPrice = parseFloat(priceText) || 0;
          }

          let vatPercentage = 0;
          const percentText = this.getText('./cac:Item/cac:ClassifiedTaxCategory/cbc:Percent', line);
          if (percentText) {
            vatPercentage = parseFloat(percentText) || 0;
          }

          // Create item with extended metadata
          const item: finance.TAccountingDocItem & { metadata?: any } = {
            position,
            name,
            articleNumber,
            unitType,
            unitQuantity,
            unitNetPrice,
            vatPercentage
          };

          // Extract order line reference
          const orderLineReference = this.getText('./cac:OrderLineReference/cac:OrderReference/cbc:ID', line) || '';
          const orderLineReferenceId = this.getText('./cac:OrderLineReference/cbc:LineID', line) || '';
          
          // Extract additional item properties
          const additionalProps: Record<string, string> = {};
          const propNodes = this.select('./cac:Item/cac:AdditionalItemProperty', line);
          if (propNodes && Array.isArray(propNodes)) {
            for (const propNode of propNodes) {
              const propName = this.getText('./cbc:Name', propNode);
              const propValue = this.getText('./cbc:Value', propNode);
              if (propName && propValue) {
                additionalProps[propName] = propValue;
              }
            }
          }
          
          // Store additional item data in metadata
          if (description || buyerItemID || standardItemID || commodityClassification || orderLineReference || Object.keys(additionalProps).length > 0) {
            item.metadata = {
              description,
              buyerItemID,
              standardItemID,
              commodityClassification,
              orderLineReference,
              orderLineReferenceId,
              additionalProperties: additionalProps
            };
          }

          items.push(item);
        }
      }

      // Extract business references
      const buyerReference = this.getText('//cbc:BuyerReference', this.doc);
      const orderReference = this.getText('//cac:OrderReference/cbc:ID', this.doc);
      const contractReference = this.getText('//cac:ContractDocumentReference/cbc:ID', this.doc);
      const projectReference = this.getText('//cac:ProjectReference/cbc:ID', this.doc);

      // Extract payment information
      const paymentMeansCode = this.getText('//cac:PaymentMeans/cbc:PaymentMeansCode', this.doc);
      const paymentID = this.getText('//cac:PaymentMeans/cbc:PaymentID', this.doc);
      const paymentDueDate = this.getText('//cac:PaymentMeans/cbc:PaymentDueDate', this.doc);
      const iban = this.getText('//cac:PaymentMeans/cac:PayeeFinancialAccount/cbc:ID', this.doc);
      const bic = this.getText('//cac:PaymentMeans/cac:PayeeFinancialAccount/cac:FinancialInstitutionBranch/cbc:ID', this.doc);
      const bankName = this.getText('//cac:PaymentMeans/cac:PayeeFinancialAccount/cac:FinancialInstitutionBranch/cbc:Name', this.doc);
      const accountName = this.getText('//cac:PaymentMeans/cac:PayeeFinancialAccount/cbc:Name', this.doc);
      
      // Extract payment terms with discount
      const paymentTermsNote = this.getText('//cac:PaymentTerms/cbc:Note', this.doc);
      const discountPercent = this.getText('//cac:PaymentTerms/cbc:SettlementDiscountPercent', this.doc);

      // Extract period information
      const periodStart = this.getText('//cac:InvoicePeriod/cbc:StartDate', this.doc);
      const periodEnd = this.getText('//cac:InvoicePeriod/cbc:EndDate', this.doc);
      const deliveryDate = this.getText('//cac:Delivery/cbc:ActualDeliveryDate', this.doc);

      // Extract notes (excluding PaymentTerms notes)
      const allNotes: string[] = [];
      const noteNodes = this.select('//cbc:Note[not(parent::cac:PaymentTerms)]', this.doc);
      if (noteNodes && Array.isArray(noteNodes)) {
        for (let i = 0; i < noteNodes.length; i++) {
          const noteText = noteNodes[i].textContent || '';
          if (noteText) {
            allNotes.push(noteText);
          }
        }
      }

      // Extract subject and notes separately
      // If we have notes, the first one might be the subject
      let subject = `Invoice ${invoiceId}`;
      let notes = [...allNotes];
      
      // If the first note doesn't look like a payment term or other standard note,
      // treat it as the subject
      if (allNotes.length > 0 && !allNotes[0].toLowerCase().includes('due in') && 
          !allNotes[0].toLowerCase().includes('payment')) {
        subject = allNotes[0];
        notes = allNotes.slice(1); // Remove subject from notes
      }

      // Extract seller and buyer information
      const seller = this.extractParty('//cac:AccountingSupplierParty/cac:Party');
      const buyer = this.extractParty('//cac:AccountingCustomerParty/cac:Party');

      // Create the common invoice data with metadata for business references
      const invoiceData: any = {
        type: 'accounting-doc' as const,
        accountingDocType: 'invoice' as const,
        id: invoiceId,
        accountingDocId: invoiceId,
        date: issueDate,
        accountingDocStatus: 'issued' as const,
        versionInfo: {
          type: 'final',
          version: '1.0.0'
        },
        language: 'en',
        incidenceId: invoiceId,
        from: seller,
        to: buyer,
        subject: subject,
        items: items,
        dueInDays: dueInDays,
        reverseCharge: false,
        currency: currencyCode as finance.TCurrency,
        notes: notes,
        objectActions: [],
        metadata: {
          format: 'xrechnung' as any,
          version: '1.0.0',
          extensions: {
            businessReferences: {
              buyerReference,
              orderReference,
              contractReference,
              projectReference
            },
            paymentInformation: {
              paymentMeansCode,
              paymentID,
              paymentDueDate,
              iban,
              bic,
              bankName,
              accountName,
              paymentTermsNote,
              discountPercent
            },
            dateInformation: {
              periodStart,
              periodEnd,
              deliveryDate
            }
          }
        }
      };
      
      // Validate mandatory EN16931 fields unless validation is skipped
      if (!this.skipValidation) {
        EN16931Validator.validateMandatoryFields(invoiceData);
      }
      
      return invoiceData;
    } catch (error) {
      // Re-throw validation errors
      if (error.message && error.message.includes('EN16931 validation failed')) {
        throw error;
      }
      
      console.error('Error extracting common data:', error);
      // Return default data
      return {
        type: 'accounting-doc' as const,
        accountingDocType: 'invoice' as const,
        id: `INV-${Date.now()}`,
        accountingDocId: `INV-${Date.now()}`,
        date: Date.now(),
        accountingDocStatus: 'issued' as const,
        versionInfo: {
          type: 'final',
          version: '1.0.0'
        },
        language: 'en',
        incidenceId: `INV-${Date.now()}`,
        from: this.createEmptyContact(),
        to: this.createEmptyContact(),
        subject: 'Invoice',
        items: [],
        dueInDays: 30,
        reverseCharge: false,
        currency: 'EUR',
        notes: [],
        objectActions: []
      };
    }
  }

  /**
   * Extracts party information from XML
   * @param partyPath XPath to the party element
   * @returns TContact object
   */
  private extractParty(partyPath: string): business.TContact {
    try {
      // Default values
      let name = '';
      let streetName = '';
      let houseNumber = '0';
      let city = '';
      let postalCode = '';
      let country = '';
      let countryCode = '';
      let vatId = '';
      let registrationId = '';
      let registrationName = '';
      let contactPhone = '';
      let contactEmail = '';
      let contactName = '';
      let gln = '';
      const additionalIdentifiers: any[] = [];

      // Try to extract party information
      const partyNodes = this.select(partyPath, this.doc);

      if (partyNodes && Array.isArray(partyNodes) && partyNodes.length > 0) {
        const party = partyNodes[0];

        // Extract GLN from EndpointID
        const endpointId = this.getText('./cbc:EndpointID[@schemeID="0088"]', party);
        if (endpointId) {
          gln = endpointId;
        }
        
        // Extract additional party identifications
        const partyIdNodes = this.select('./cac:PartyIdentification', party);
        if (partyIdNodes && Array.isArray(partyIdNodes)) {
          for (const idNode of partyIdNodes) {
            const idValue = this.getText('./cbc:ID', idNode);
            const idElement = (idNode as Element).getElementsByTagName('cbc:ID')[0];
            const schemeId = idElement?.getAttribute('schemeID');
            if (idValue) {
              additionalIdentifiers.push({
                value: idValue,
                scheme: schemeId || ''
              });
            }
          }
        }

        // Extract name
        name = this.getText('./cac:PartyName/cbc:Name', party) || '';

        // Extract address
        const addressNodes = this.select('./cac:PostalAddress', party);
        if (addressNodes && Array.isArray(addressNodes) && addressNodes.length > 0) {
          const address = addressNodes[0];

          streetName = this.getText('./cbc:StreetName', address) || '';
          houseNumber = this.getText('./cbc:BuildingNumber', address) || '0';
          city = this.getText('./cbc:CityName', address) || '';
          postalCode = this.getText('./cbc:PostalZone', address) || '';

          const countryNodes = this.select('./cac:Country', address);
          if (countryNodes && Array.isArray(countryNodes) && countryNodes.length > 0) {
            const countryNode = countryNodes[0];
            country = this.getText('./cbc:Name', countryNode) || '';
            countryCode = this.getText('./cbc:IdentificationCode', countryNode) || '';
          }
        }

        // Extract tax information
        const taxSchemeNodes = this.select('./cac:PartyTaxScheme', party);
        if (taxSchemeNodes && Array.isArray(taxSchemeNodes) && taxSchemeNodes.length > 0) {
          vatId = this.getText('./cbc:CompanyID', taxSchemeNodes[0]) || '';
        }

        // Extract registration information
        const legalEntityNodes = this.select('./cac:PartyLegalEntity', party);
        if (legalEntityNodes && Array.isArray(legalEntityNodes) && legalEntityNodes.length > 0) {
          registrationId = this.getText('./cbc:CompanyID', legalEntityNodes[0]) || '';
          registrationName = this.getText('./cbc:RegistrationName', legalEntityNodes[0]) || name;
        }

        // Extract contact information
        const contactNodes = this.select('./cac:Contact', party);
        if (contactNodes && Array.isArray(contactNodes) && contactNodes.length > 0) {
          const contact = contactNodes[0];
          contactPhone = this.getText('./cbc:Telephone', contact) || '';
          contactEmail = this.getText('./cbc:ElectronicMail', contact) || '';
          contactName = this.getText('./cbc:Name', contact) || '';
        }
      }

      // Create contact with additional metadata for contact information
      const contact: business.TContact & { metadata?: any } = {
        type: 'company',
        name: name,
        description: '',
        address: {
          streetName: streetName,
          houseNumber: houseNumber,
          city: city,
          postalCode: postalCode,
          country: country,
          countryCode: countryCode
        },
        status: 'active',
        foundedDate: {
          year: 2000,
          month: 1,
          day: 1
        },
        registrationDetails: {
          vatId: vatId,
          registrationId: registrationId,
          registrationName: registrationName
        }
      };

      // Store contact information and additional identifiers in metadata if available
      const metadata: any = {};
      
      if (contactPhone || contactEmail || contactName) {
        metadata.contactInformation = {
          phone: contactPhone,
          email: contactEmail,
          name: contactName
        };
      }
      
      if (gln) {
        (contact as any).gln = gln;
      }
      
      if (additionalIdentifiers.length > 0) {
        (contact as any).additionalIdentifiers = additionalIdentifiers;
      }
      
      if (Object.keys(metadata).length > 0) {
        contact.metadata = metadata;
      }

      return contact;
    } catch (error) {
      console.error('Error extracting party information:', error);
      return this.createEmptyContact();
    }
  }

  /**
   * Creates an empty TContact object
   * @returns Empty TContact object
   */
  private createEmptyContact(): business.TContact {
    return {
      type: 'company',
      name: '',
      description: '',
      address: {
        streetName: '',
        houseNumber: '0',
        city: '',
        country: '',
        postalCode: ''
      },
      status: 'active',
      foundedDate: {
        year: 2000,
        month: 1,
        day: 1
      },
      registrationDetails: {
        vatId: '',
        registrationId: '',
        registrationName: ''
      }
    };
  }
}
