import { BaseValidator } from '../base/base.validator.js';
import { InvoiceFormat, ValidationLevel } from '../../interfaces/common.js';
import type { ValidationResult } from '../../interfaces/common.js';
import { FormatDetector } from '../utils/format.detector.js';

// Import specific validators
import { UBLBaseValidator } from '../ubl/ubl.validator.js';
import { FacturXValidator } from '../cii/facturx/facturx.validator.js';
import { ZUGFeRDValidator } from '../cii/zugferd/zugferd.validator.js';

/**
 * UBL validator implementation
 * Provides validation for standard UBL documents
 */
class UBLValidator extends UBLBaseValidator {
  protected validateStructure(): boolean {
    // Basic validation to check for required UBL invoice elements
    if (!this.doc) return false;

    let valid = true;

    // Check for required UBL elements
    const requiredElements = [
      'cbc:ID',
      'cbc:IssueDate',
      'cac:AccountingSupplierParty',
      'cac:AccountingCustomerParty'
    ];

    for (const element of requiredElements) {
      if (!this.exists(`//${element}`)) {
        this.addError(
          'UBL-STRUCT-1',
          `Required element ${element} is missing`,
          `/${element}`
        );
        valid = false;
      }
    }

    return valid;
  }

  protected validateBusinessRules(): boolean {
    // Basic business rule validation for UBL
    if (!this.doc) return false;

    let valid = true;

    // Check that issue date is present and valid
    const issueDateText = this.getText('//cbc:IssueDate');
    if (!issueDateText) {
      this.addError(
        'UBL-BUS-1',
        'Issue date is required',
        '//cbc:IssueDate'
      );
      valid = false;
    } else {
      const issueDate = new Date(issueDateText);
      if (isNaN(issueDate.getTime())) {
        this.addError(
          'UBL-BUS-2',
          'Issue date is not a valid date',
          '//cbc:IssueDate'
        );
        valid = false;
      }
    }

    // Check that at least one invoice line exists
    if (!this.exists('//cac:InvoiceLine') && !this.exists('//cac:CreditNoteLine')) {
      this.addError(
        'UBL-BUS-3',
        'At least one invoice line or credit note line is required',
        '/'
      );
      valid = false;
    }

    return valid;
  }
}

/**
 * XRechnung validator implementation
 * Extends UBL validator with additional XRechnung specific validation rules
 */
class XRechnungValidator extends UBLValidator {
  protected validateStructure(): boolean {
    // Call the base UBL validation first
    const baseValid = super.validateStructure();
    let valid = baseValid;

    // Check for XRechnung-specific elements
    if (!this.exists('//cbc:CustomizationID[contains(text(), "xrechnung")]')) {
      this.addError(
        'XRECH-STRUCT-1',
        'XRechnung customization ID is missing or invalid',
        '//cbc:CustomizationID'
      );
      valid = false;
    }

    // Check for buyer reference which is mandatory in XRechnung
    if (!this.exists('//cbc:BuyerReference')) {
      this.addError(
        'XRECH-STRUCT-2',
        'BuyerReference is required in XRechnung',
        '//'
      );
      valid = false;
    }

    return valid;
  }

  protected validateBusinessRules(): boolean {
    // Call the base UBL business rule validation
    const baseValid = super.validateBusinessRules();
    let valid = baseValid;

    // German-specific validation rules
    // Check for proper VAT ID structure for German VAT IDs
    const supplierVatId = this.getText('//cac:AccountingSupplierParty//cbc:CompanyID[../cac:TaxScheme/cbc:ID="VAT"]');
    if (supplierVatId && supplierVatId.startsWith('DE') && !/^DE[0-9]{9}$/.test(supplierVatId)) {
      this.addError(
        'XRECH-BUS-1',
        'German VAT ID format is invalid (must be DE followed by 9 digits)',
        '//cac:AccountingSupplierParty//cbc:CompanyID'
      );
      valid = false;
    }

    return valid;
  }
}

/**
 * FatturaPA validator implementation
 * Basic implementation for Italian electronic invoices
 */
class FatturaPAValidator extends BaseValidator {
  validate(level: ValidationLevel = ValidationLevel.SYNTAX): ValidationResult {
    // Reset errors
    this.errors = [];
    
    let valid = true;
    
    if (level === ValidationLevel.SYNTAX) {
      valid = this.validateSchema();
    } else if (level === ValidationLevel.SEMANTIC || level === ValidationLevel.BUSINESS) {
      valid = this.validateSchema() && this.validateBusinessRules();
    }
    
    return {
      valid,
      errors: this.errors,
      level
    };
  }

  protected validateSchema(): boolean {
    // Basic schema validation for FatturaPA
    if (!this.xml.includes('<FatturaElettronica')) {
      this.addError('FATT-SCHEMA-1', 'Root element must be FatturaElettronica', '/');
      return false;
    }
    return true;
  }

  protected validateBusinessRules(): boolean {
    // Basic placeholder implementation - would need more detailed rules
    // for a real implementation
    return this.validateSchema();
  }
}

/**
 * Factory to create the appropriate validator based on the XML format
 */
export class ValidatorFactory {
  /**
   * Creates a validator for the specified XML content
   * @param xml XML content to validate
   * @returns Appropriate validator instance
   */
  public static createValidator(xml: string): BaseValidator {
    try {
      const format = FormatDetector.detectFormat(xml);

      switch (format) {
        case InvoiceFormat.UBL:
          return new UBLValidator(xml);

        case InvoiceFormat.XRECHNUNG:
          return new XRechnungValidator(xml);

        case InvoiceFormat.CII:
          // For now, use Factur-X validator for generic CII
          return new FacturXValidator(xml);

        case InvoiceFormat.ZUGFERD:
          return new ZUGFeRDValidator(xml);

        case InvoiceFormat.FACTURX:
          return new FacturXValidator(xml);

        case InvoiceFormat.FATTURAPA:
          return new FatturaPAValidator(xml);

        default:
          // For unknown formats, provide a generic validator that will
          // mark the document as invalid but won't throw an exception
          return new GenericValidator(xml, format);
      }
    } catch (error) {
      // If an error occurs during validator creation, return a generic validator
      // that will provide meaningful error information instead of throwing
      console.error(`Error creating validator: ${error}`);
      return new GenericValidator(xml, 'unknown');
    }
  }
}

/**
 * Generic validator for unknown or unsupported formats
 * Provides meaningful validation errors instead of throwing exceptions
 */
class GenericValidator extends BaseValidator {
  private format: string;

  constructor(xml: string, format: string) {
    super(xml);
    this.format = format;
    this.addError(
      'GEN-1',
      `Unsupported invoice format: ${format}`,
      '/'
    );
  }

  validate(level: ValidationLevel = ValidationLevel.SYNTAX): ValidationResult {
    return {
      valid: false,
      errors: this.errors,
      level
    };
  }

  protected validateSchema(): boolean {
    return false;
  }

  protected validateBusinessRules(): boolean {
    return false;
  }
}