import * as plugins from '../plugins.js';
import * as xmldom from 'xmldom';
import { BaseDecoder } from './base.decoder.js';

/**
 * A decoder specifically for XInvoice/XRechnung format.
 * XRechnung is the German implementation of the European standard EN16931 
 * for electronic invoices to the German public sector.
 */
export class XInvoiceDecoder extends BaseDecoder {
  private xmlDoc: Document | null = null;
  private namespaces: { [key: string]: string } = {
    cbc: 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2',
    cac: 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
    ubl: 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'
  };

  constructor(xmlString: string) {
    super(xmlString);
    
    // Parse XML to DOM
    try {
      const parser = new xmldom.DOMParser();
      this.xmlDoc = parser.parseFromString(this.xmlString, 'text/xml');
      
      // Try to detect if this is actually UBL (which XRechnung is based on)
      if (this.xmlString.includes('oasis:names:specification:ubl')) {
        // Set up appropriate namespaces
        this.setupNamespaces();
      }
    } catch (error) {
      console.error('Error parsing XInvoice XML:', error);
    }
  }

  /**
   * Set up namespaces from the document
   */
  private setupNamespaces(): void {
    if (!this.xmlDoc) return;
    
    // Try to extract namespaces from the document
    const root = this.xmlDoc.documentElement;
    if (root) {
      // Look for common UBL namespaces
      for (let i = 0; i < root.attributes.length; i++) {
        const attr = root.attributes[i];
        if (attr.name.startsWith('xmlns:')) {
          const prefix = attr.name.substring(6);
          this.namespaces[prefix] = attr.value;
        }
      }
    }
  }

  /**
   * Extract element text by tag name with namespace awareness
   */
  private getElementText(tagName: string): string {
    if (!this.xmlDoc) {
      return '';
    }
    
    try {
      // Handle namespace prefixes
      if (tagName.includes(':')) {
        const [nsPrefix, localName] = tagName.split(':');
        
        // Find elements with this tag name
        const elements = this.xmlDoc.getElementsByTagNameNS(this.namespaces[nsPrefix] || '', localName);
        if (elements.length > 0) {
          return elements[0].textContent || '';
        }
      }
      
      // Fallback to direct tag name lookup
      const elements = this.xmlDoc.getElementsByTagName(tagName);
      if (elements.length > 0) {
        return elements[0].textContent || '';
      }
      
      return '';
    } catch (error) {
      console.error(`Error extracting XInvoice element ${tagName}:`, error);
      return '';
    }
  }

  /**
   * Converts XInvoice/XRechnung XML to a structured letter object
   */
  public async getLetterData(): Promise<plugins.tsclass.business.ILetter> {
    try {
      // Extract invoice ID - typically in cbc:ID or Invoice/cbc:ID
      let invoiceId = this.getElementText('cbc:ID');
      if (!invoiceId) {
        invoiceId = this.getElementText('Invoice/cbc:ID') || 'Unknown';
      }
      
      // Extract invoice issue date
      const issueDateStr = this.getElementText('cbc:IssueDate') || '';
      const issueDate = issueDateStr ? new Date(issueDateStr).getTime() : Date.now();
      
      // Extract seller information
      const sellerName = this.getElementText('cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name') || 
                        this.getElementText('cac:SellerSupplierParty/cac:Party/cac:PartyName/cbc:Name') ||
                        'Unknown Seller';
      
      // Extract seller address
      const sellerStreet = this.getElementText('cac:AccountingSupplierParty/cac:Party/cac:PostalAddress/cbc:StreetName') || 'Unknown';
      const sellerCity = this.getElementText('cac:AccountingSupplierParty/cac:Party/cac:PostalAddress/cbc:CityName') || 'Unknown';
      const sellerPostcode = this.getElementText('cac:AccountingSupplierParty/cac:Party/cac:PostalAddress/cbc:PostalZone') || 'Unknown';
      const sellerCountry = this.getElementText('cac:AccountingSupplierParty/cac:Party/cac:PostalAddress/cac:Country/cbc:IdentificationCode') || 'Unknown';
      
      // Extract buyer information
      const buyerName = this.getElementText('cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name') ||
                        this.getElementText('cac:BuyerCustomerParty/cac:Party/cac:PartyName/cbc:Name') ||
                        'Unknown Buyer';
                        
      // Create seller contact
      const seller: plugins.tsclass.business.IContact = {
        name: sellerName,
        type: 'company',
        description: sellerName,
        address: {
          streetName: sellerStreet,
          houseNumber: '0',  // Required by IAddress interface
          city: sellerCity,
          country: sellerCountry,
          postalCode: sellerPostcode,
        },
      };
      
      // Create buyer contact
      const buyer: plugins.tsclass.business.IContact = {
        name: buyerName,
        type: 'company',
        description: buyerName,
        address: {
          streetName: 'Unknown',
          houseNumber: '0',
          city: 'Unknown',
          country: 'Unknown',
          postalCode: 'Unknown',
        },
      };
      
      // Extract invoice type
      let invoiceType = 'debitnote';
      const typeCode = this.getElementText('cbc:InvoiceTypeCode');
      if (typeCode === '380') {
        invoiceType = 'debitnote'; // Standard invoice
      } else if (typeCode === '381') {
        invoiceType = 'creditnote'; // Credit note
      }
      
      // Create invoice data
      const invoiceData: plugins.tsclass.finance.IInvoice = {
        id: invoiceId,
        status: null,
        type: invoiceType as 'debitnote' | 'creditnote',
        billedBy: seller,
        billedTo: buyer,
        deliveryDate: issueDate,
        dueInDays: 30,
        periodOfPerformance: null,
        printResult: null,
        currency: (this.getElementText('cbc:DocumentCurrencyCode') || 'EUR') as plugins.tsclass.finance.TCurrency,
        notes: [],
        items: this.extractInvoiceItems(),
        reverseCharge: false,
      };
      
      // Return a letter
      return {
        versionInfo: {
          type: 'draft',
          version: '1.0.0',
        },
        type: 'invoice',
        date: issueDate,
        subject: `XInvoice: ${invoiceId}`,
        from: seller,
        to: buyer,
        content: {
          invoiceData: invoiceData,
          textData: null,
          timesheetData: null,
          contractData: null,
        },
        needsCoverSheet: false,
        objectActions: [],
        pdf: null,
        incidenceId: null,
        language: null,
        legalContact: null,
        logoUrl: null,
        pdfAttachments: null,
        accentColor: null,
      };
    } catch (error) {
      console.error('Error converting XInvoice XML to letter data:', error);
      return this.createDefaultLetter();
    }
  }
  
  /**
   * Extracts invoice items from XInvoice document
   */
  private extractInvoiceItems(): plugins.tsclass.finance.IInvoiceItem[] {
    if (!this.xmlDoc) {
      return [
        {
          name: 'Unknown Item',
          unitQuantity: 1,
          unitNetPrice: 0,
          vatPercentage: 0,
          position: 0,
          unitType: 'units',
        }
      ];
    }
    
    try {
      const items: plugins.tsclass.finance.IInvoiceItem[] = [];
      
      // Get all invoice line elements
      const lines = this.xmlDoc.getElementsByTagName('cac:InvoiceLine');
      if (!lines || lines.length === 0) {
        // Fallback to a default item
        return [
          {
            name: 'Item from XInvoice XML',
            unitQuantity: 1,
            unitNetPrice: 0,
            vatPercentage: 0,
            position: 0,
            unitType: 'units',
          }
        ];
      }
      
      // Process each line
      for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        
        // Extract item details
        let name = '';
        let quantity = 1;
        let price = 0;
        let vatRate = 0;
        
        // Find description element
        const descElements = line.getElementsByTagName('cbc:Description');
        if (descElements.length > 0) {
          name = descElements[0].textContent || '';
        }
        
        // Fallback to item name if description is empty
        if (!name) {
          const itemNameElements = line.getElementsByTagName('cbc:Name');
          if (itemNameElements.length > 0) {
            name = itemNameElements[0].textContent || '';
          }
        }
        
        // Find quantity
        const quantityElements = line.getElementsByTagName('cbc:InvoicedQuantity');
        if (quantityElements.length > 0) {
          const quantityText = quantityElements[0].textContent || '1';
          quantity = parseFloat(quantityText) || 1;
        }
        
        // Find price
        const priceElements = line.getElementsByTagName('cbc:PriceAmount');
        if (priceElements.length > 0) {
          const priceText = priceElements[0].textContent || '0';
          price = parseFloat(priceText) || 0;
        }
        
        // Find VAT rate - this is a bit more complex in UBL/XRechnung
        const taxCategoryElements = line.getElementsByTagName('cac:ClassifiedTaxCategory');
        if (taxCategoryElements.length > 0) {
          const rateElements = taxCategoryElements[0].getElementsByTagName('cbc:Percent');
          if (rateElements.length > 0) {
            const rateText = rateElements[0].textContent || '0';
            vatRate = parseFloat(rateText) || 0;
          }
        }
        
        // Add the item to the list
        items.push({
          name: name || `Item ${i+1}`,
          unitQuantity: quantity,
          unitNetPrice: price,
          vatPercentage: vatRate,
          position: i,
          unitType: 'units',
        });
      }
      
      return items.length > 0 ? items : [
        {
          name: 'Item from XInvoice XML',
          unitQuantity: 1,
          unitNetPrice: 0,
          vatPercentage: 0,
          position: 0,
          unitType: 'units',
        }
      ];
    } catch (error) {
      console.error('Error extracting XInvoice items:', error);
      return [
        {
          name: 'Error extracting items',
          unitQuantity: 1,
          unitNetPrice: 0,
          vatPercentage: 0,
          position: 0,
          unitType: 'units',
        }
      ];
    }
  }
}