import { BaseValidator } from '../base/base.validator.js';
import { ValidationLevel } from '../../interfaces/common.js';
import type { ValidationResult } from '../../interfaces/common.js';
import { UBLDocumentType } from './ubl.types.js';
import { DOMParser } from 'xmldom';
import * as xpath from 'xpath';

/**
 * Base validator for UBL-based invoice formats
 */
export abstract class UBLBaseValidator extends BaseValidator {
  protected doc: Document;
  protected namespaces: Record<string, string>;
  protected select: xpath.XPathSelect;

  constructor(xml: string) {
    super(xml);

    try {
      // Parse XML document
      this.doc = new DOMParser().parseFromString(xml, 'application/xml');

      // Set up namespaces for XPath queries
      this.namespaces = {
        cbc: 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2',
        cac: 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'
      };

      // Create XPath selector with namespaces
      this.select = xpath.useNamespaces(this.namespaces);
    } catch (error) {
      this.addError('UBL-PARSE', `Failed to parse XML: ${error}`, '/');
    }
  }

  /**
   * Validates UBL XML against the specified level of validation
   * @param level Validation level
   * @returns Result of validation
   */
  public validate(level: ValidationLevel = ValidationLevel.SYNTAX): ValidationResult {
    // Reset errors
    this.errors = [];

    // Check if document was parsed successfully
    if (!this.doc) {
      return {
        valid: false,
        errors: this.errors,
        level: level
      };
    }

    // Perform validation based on level
    let valid = true;

    if (level === ValidationLevel.SYNTAX) {
      valid = this.validateSchema();
    } else if (level === ValidationLevel.SEMANTIC) {
      valid = this.validateSchema() && this.validateStructure();
    } else if (level === ValidationLevel.BUSINESS) {
      valid = this.validateSchema() &&
              this.validateStructure() &&
              this.validateBusinessRules();
    }

    return {
      valid,
      errors: this.errors,
      level
    };
  }

  /**
   * Validates UBL XML against schema
   * @returns True if schema validation passed
   */
  protected validateSchema(): boolean {
    // Basic schema validation (simplified for now)
    if (!this.doc) return false;

    // Check for root element
    const root = this.doc.documentElement;
    if (!root || (root.nodeName !== UBLDocumentType.INVOICE && root.nodeName !== UBLDocumentType.CREDIT_NOTE)) {
      this.addError('UBL-SCHEMA-1', `Root element must be ${UBLDocumentType.INVOICE} or ${UBLDocumentType.CREDIT_NOTE}`, '/');
      return false;
    }

    return true;
  }

  /**
   * Validates structure of the UBL XML document
   * @returns True if structure validation passed
   */
  protected abstract validateStructure(): boolean;

  /**
   * Gets a text value from an XPath expression
   * @param xpath XPath expression
   * @param context Optional context node
   * @returns Text value or empty string if not found
   */
  protected getText(xpathExpr: string, context?: Node): string {
    const node = this.select(xpathExpr, context || this.doc)[0];
    return node ? (node.textContent || '') : '';
  }

  /**
   * Gets a number value from an XPath expression
   * @param xpath XPath expression
   * @param context Optional context node
   * @returns Number value or 0 if not found or not a number
   */
  protected getNumber(xpathExpr: string, context?: Node): number {
    const text = this.getText(xpathExpr, context);
    const num = parseFloat(text);
    return isNaN(num) ? 0 : num;
  }

  /**
   * Checks if a node exists
   * @param xpath XPath expression
   * @param context Optional context node
   * @returns True if node exists
   */
  protected exists(xpathExpr: string, context?: Node): boolean {
    const nodes = this.select(xpathExpr, context || this.doc);
    if (Array.isArray(nodes)) {
      return nodes.length > 0;
    }
    return false;
  }
}