import { UBLBaseValidator } from './ubl.validator.js';
import { ValidationLevel } from '../../interfaces/common.js';
import { xpath } from '../../plugins.js';

/**
 * EN16931-compliant UBL validator that implements all business rules
 */
export class EN16931UBLValidator extends UBLBaseValidator {
  /**
   * Validates the structure of the UBL document
   */
  protected validateStructure(): boolean {
    let valid = true;

    // Check for required elements
    const requiredElements = [
      { path: '//cbc:ID', error: 'Required element cbc:ID is missing' },
      { path: '//cbc:IssueDate', error: 'Required element cbc:IssueDate is missing' },
      { path: '//cbc:CustomizationID', error: 'Required element cbc:CustomizationID is missing' }
    ];

    for (const element of requiredElements) {
      if (!this.exists(element.path)) {
        this.addError('STRUCT-REQUIRED', element.error, element.path);
        valid = false;
      }
    }

    // Check for at least one invoice line or credit note line
    const invoiceLines = this.select('//cac:InvoiceLine', this.doc) as Node[];
    const creditNoteLines = this.select('//cac:CreditNoteLine', this.doc) as Node[];
    
    if (invoiceLines.length === 0 && creditNoteLines.length === 0) {
      this.addError('STRUCT-LINE', 'At least one invoice line or credit note line is required', '/');
      valid = false;
    }

    return valid;
  }

  /**
   * Validates EN16931 business rules
   */
  protected validateBusinessRules(): boolean {
    let valid = true;

    // BR-01: An Invoice shall have a Specification identifier (BT-24).
    if (!this.exists('//cbc:CustomizationID')) {
      this.addError('BR-01', 'An Invoice shall have a Specification identifier', '//cbc:CustomizationID');
      valid = false;
    }

    // BR-02: An Invoice shall have an Invoice number (BT-1).
    if (!this.exists('//cbc:ID')) {
      this.addError('BR-02', 'An Invoice shall have an Invoice number', '//cbc:ID');
      valid = false;
    }

    // BR-03: An Invoice shall have an Invoice issue date (BT-2).
    if (!this.exists('//cbc:IssueDate')) {
      this.addError('BR-03', 'An Invoice shall have an Invoice issue date', '//cbc:IssueDate');
      valid = false;
    }

    // BR-04: An Invoice shall have an Invoice type code (BT-3).
    const isInvoice = this.doc.documentElement.localName === 'Invoice';
    if (isInvoice && !this.exists('//cbc:InvoiceTypeCode')) {
      this.addError('BR-04', 'An Invoice shall have an Invoice type code', '//cbc:InvoiceTypeCode');
      valid = false;
    }

    // BR-05: An Invoice shall have an Invoice currency code (BT-5).
    if (!this.exists('//cbc:DocumentCurrencyCode')) {
      this.addError('BR-05', 'An Invoice shall have an Invoice currency code', '//cbc:DocumentCurrencyCode');
      valid = false;
    }

    // BR-06: An Invoice shall contain the Seller name (BT-27).
    if (!this.exists('//cac:AccountingSupplierParty//cbc:RegistrationName') && 
        !this.exists('//cac:AccountingSupplierParty//cbc:Name')) {
      this.addError('BR-06', 'An Invoice shall contain the Seller name', '//cac:AccountingSupplierParty');
      valid = false;
    }

    // BR-07: An Invoice shall contain the Buyer name (BT-44).
    if (!this.exists('//cac:AccountingCustomerParty//cbc:RegistrationName') && 
        !this.exists('//cac:AccountingCustomerParty//cbc:Name')) {
      this.addError('BR-07', 'An Invoice shall contain the Buyer name', '//cac:AccountingCustomerParty');
      valid = false;
    }

    // BR-08: An Invoice shall contain the Seller postal address (BG-5).
    const sellerAddress = this.select('//cac:AccountingSupplierParty//cac:PostalAddress', this.doc)[0];
    if (!sellerAddress || !this.exists('.//cbc:IdentificationCode', sellerAddress)) {
      this.addError('BR-08', 'An Invoice shall contain the Seller postal address', '//cac:AccountingSupplierParty//cac:PostalAddress');
      valid = false;
    }

    // BR-09: The Seller postal address (BG-5) shall contain a Seller country code (BT-40).
    if (sellerAddress && !this.exists('.//cac:Country/cbc:IdentificationCode', sellerAddress)) {
      this.addError('BR-09', 'The Seller postal address shall contain a Seller country code', '//cac:AccountingSupplierParty//cac:PostalAddress//cac:Country');
      valid = false;
    }

    // BR-10: An Invoice shall contain the Buyer postal address (BG-8).
    const buyerAddress = this.select('//cac:AccountingCustomerParty//cac:PostalAddress', this.doc)[0];
    if (!buyerAddress || !this.exists('.//cbc:IdentificationCode', buyerAddress)) {
      this.addError('BR-10', 'An Invoice shall contain the Buyer postal address', '//cac:AccountingCustomerParty//cac:PostalAddress');
      valid = false;
    }

    // BR-11: The Buyer postal address (BG-8) shall contain a Buyer country code (BT-55).
    if (buyerAddress && !this.exists('.//cac:Country/cbc:IdentificationCode', buyerAddress)) {
      this.addError('BR-11', 'The Buyer postal address shall contain a Buyer country code', '//cac:AccountingCustomerParty//cac:PostalAddress//cac:Country');
      valid = false;
    }

    // BR-12: An Invoice shall have the Sum of Invoice line net amount (BT-106).
    if (!this.exists('//cac:LegalMonetaryTotal/cbc:LineExtensionAmount')) {
      this.addError('BR-12', 'An Invoice shall have the Sum of Invoice line net amount', '//cac:LegalMonetaryTotal/cbc:LineExtensionAmount');
      valid = false;
    }

    // BR-13: An Invoice shall have the Invoice total amount without VAT (BT-109).
    if (!this.exists('//cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount')) {
      this.addError('BR-13', 'An Invoice shall have the Invoice total amount without VAT', '//cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount');
      valid = false;
    }

    // BR-14: An Invoice shall have the Invoice total amount with VAT (BT-112).
    if (!this.exists('//cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount')) {
      this.addError('BR-14', 'An Invoice shall have the Invoice total amount with VAT', '//cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount');
      valid = false;
    }

    // BR-15: An Invoice shall have the Amount due for payment (BT-115).
    if (!this.exists('//cac:LegalMonetaryTotal/cbc:PayableAmount')) {
      this.addError('BR-15', 'An Invoice shall have the Amount due for payment', '//cac:LegalMonetaryTotal/cbc:PayableAmount');
      valid = false;
    }

    // BR-16: An Invoice shall have at least one Invoice line (BG-25).
    const lines = this.select('//cac:InvoiceLine | //cac:CreditNoteLine', this.doc) as Node[];
    if (lines.length === 0) {
      this.addError('BR-16', 'An Invoice shall have at least one Invoice line', '//cac:InvoiceLine');
      valid = false;
    }

    // Validate calculation rules if we have the necessary data
    if (this.exists('//cac:LegalMonetaryTotal/cbc:LineExtensionAmount')) {
      valid = this.validateCalculationRules() && valid;
    }

    return valid;
  }

  /**
   * Validates calculation rules (BR-CO-*)
   */
  private validateCalculationRules(): boolean {
    let valid = true;

    // BR-CO-10: Sum of Invoice line net amount = Σ Invoice line net amount.
    const lineExtensionAmount = this.getNumber('//cac:LegalMonetaryTotal/cbc:LineExtensionAmount');
    const lines = this.select('//cac:InvoiceLine | //cac:CreditNoteLine', this.doc) as Node[];
    
    let calculatedSum = 0;
    for (const line of lines) {
      const lineAmount = this.getNumber('.//cbc:LineExtensionAmount', line);
      calculatedSum += lineAmount;
    }

    // Allow for small rounding differences (0.01)
    if (Math.abs(lineExtensionAmount - calculatedSum) > 0.01) {
      this.addError(
        'BR-CO-10', 
        `Sum of Invoice line net amount (${lineExtensionAmount}) must equal sum of line amounts (${calculatedSum})`,
        '//cac:LegalMonetaryTotal/cbc:LineExtensionAmount'
      );
      valid = false;
    }

    // BR-CO-13: Invoice total amount without VAT = Σ Invoice line net amount - Sum of allowances on document level + Sum of charges on document level.
    const taxExclusiveAmount = this.getNumber('//cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount');
    const allowanceTotal = this.getNumber('//cac:LegalMonetaryTotal/cbc:AllowanceTotalAmount') || 0;
    const chargeTotal = this.getNumber('//cac:LegalMonetaryTotal/cbc:ChargeTotalAmount') || 0;
    
    const calculatedTaxExclusive = lineExtensionAmount - allowanceTotal + chargeTotal;
    
    if (Math.abs(taxExclusiveAmount - calculatedTaxExclusive) > 0.01) {
      this.addError(
        'BR-CO-13',
        `Invoice total amount without VAT (${taxExclusiveAmount}) must equal calculated amount (${calculatedTaxExclusive})`,
        '//cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount'
      );
      valid = false;
    }

    // BR-CO-15: Invoice total amount with VAT = Invoice total amount without VAT + Invoice total VAT amount.
    const taxInclusiveAmount = this.getNumber('//cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount');
    const totalTaxAmount = this.getNumber('//cac:TaxTotal/cbc:TaxAmount') || 0;
    
    const calculatedTaxInclusive = taxExclusiveAmount + totalTaxAmount;
    
    if (Math.abs(taxInclusiveAmount - calculatedTaxInclusive) > 0.01) {
      this.addError(
        'BR-CO-15',
        `Invoice total amount with VAT (${taxInclusiveAmount}) must equal calculated amount (${calculatedTaxInclusive})`,
        '//cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount'
      );
      valid = false;
    }

    return valid;
  }
}