import { AccessibilityBrowser } from './accessibility-browser';
import type { 
  AccessibilityScanOptions, 
  AccessibilityScanResult,
  AccessibilityViolation,
  ScanSummary,
  PageMetadata,
  WCAGCompliance,
  WCAGLevel,
  CriteriaResult
} from '@wcag-ada/shared';
import { 
  calculateComplianceScore,
  summarizeViolations,
  generateFixSuggestion,
  extractPageMetadata,
  isCompliantWithLevel,
  CRITICAL_WCAG_CRITERIA
} from '@wcag-ada/shared';
import type { AxeResults, Result as AxeResult } from 'axe-core';
import { initializeWcagConfig } from '@wcag-ada/config';

export class AccessibilityScanner {
  private browser: AccessibilityBrowser;
  private logger: any;

  constructor(logger?: any) {
    this.logger = logger || console;
    
    // Initialize config for scanner service
    initializeWcagConfig();
    
    this.browser = new AccessibilityBrowser(logger);
  }

  async initialize(): Promise<void> {
    await this.browser.initialize();
  }

  async close(): Promise<void> {
    await this.browser.close();
  }

  async scan(options: AccessibilityScanOptions): Promise<AccessibilityScanResult> {
    const startTime = Date.now();
    
    try {
      // Perform the scan
      const { page, axeResults, html } = await this.browser.scanPage(options);
      
      // Capture screenshots if requested
      let screenshotMap: Map<string, string> | undefined;
      if (options.includeScreenshots) {
        screenshotMap = await this.browser.captureViolationScreenshots(page, axeResults.violations);
      }
      
      // Evaluate custom rules if provided
      let customViolations: AxeResult[] = [];
      if (options.customRules && options.customRules.length > 0) {
        customViolations = await this.browser.evaluateCustomRules(page, options.customRules);
      }
      
      // Close the page
      await page.close();
      
      // Process results
      const violations = this.processViolations(
        [...axeResults.violations, ...customViolations],
        screenshotMap
      );
      
      // Extract metadata
      const pageMetadata = this.extractPageMetadata(html, options);
      
      // Calculate summary
      const summary = this.calculateSummary(axeResults, violations);
      
      // Determine WCAG compliance
      const wcagCompliance = this.calculateWCAGCompliance(
        violations,
        axeResults.passes,
        options.wcagLevel || { level: 'AA', version: '2.1' },
        summary.score
      );
      
      const scanDuration = Date.now() - startTime;
      
      return {
        url: options.url,
        timestamp: new Date(),
        scanDuration,
        summary,
        violations,
        passes: axeResults.passes,
        incomplete: axeResults.incomplete,
        inapplicable: axeResults.inapplicable,
        pageMetadata,
        wcagCompliance,
      };
    } catch (error) {
      this.logger.error('Scan failed:', error);
      throw error;
    }
  }

  private processViolations(
    axeViolations: AxeResult[],
    screenshots?: Map<string, string>
  ): AccessibilityViolation[] {
    return axeViolations.map(violation => {
      const nodes = violation.nodes.map((node, index) => {
        const screenshotKey = `${violation.id}_${index}`;
        return {
          html: node.html,
          target: node.target as string[],
          xpath: node.xpath?.[0],
          failureSummary: node.failureSummary,
          screenshot: screenshots?.get(screenshotKey),
          relatedNodes: node.relatedNodes?.map(related => ({
            html: related.html,
            target: related.target as string[],
          })),
        };
      });

      return {
        id: violation.id,
        impact: violation.impact!,
        description: violation.description,
        help: violation.help,
        helpUrl: violation.helpUrl,
        tags: violation.tags,
        nodes,
        wcagCriteria: this.mapToWCAGCriteria(violation.tags),
        fixSuggestion: generateFixSuggestion(violation.id),
      };
    });
  }

  private extractPageMetadata(html: string, options: AccessibilityScanOptions): PageMetadata {
    const metadata = extractPageMetadata(html);
    
    return {
      title: metadata.title || 'Untitled Page',
      url: options.url,
      viewport: options.viewport || { width: 1280, height: 720 },
      language: metadata.language,
      doctype: this.extractDoctype(html),
      hasLandmarks: metadata.hasLandmarks || false,
      hasHeadings: metadata.hasHeadings || false,
      imagesCount: metadata.imagesCount || 0,
      formsCount: metadata.formsCount || 0,
      linksCount: metadata.linksCount || 0,
    };
  }

  private extractDoctype(html: string): string {
    const doctypeMatch = html.match(/<!DOCTYPE\s+([^>]+)>/i);
    return doctypeMatch ? doctypeMatch[1].trim() : 'unknown';
  }

  private calculateSummary(
    axeResults: AxeResults,
    violations: AccessibilityViolation[]
  ): ScanSummary {
    const violationSummary = summarizeViolations(violations);
    const violationCount = violations.reduce((sum, v) => sum + v.nodes.length, 0);
    
    const result: AccessibilityScanResult = {
      violations,
      passes: axeResults.passes,
      summary: {} as ScanSummary,
    } as AccessibilityScanResult;
    
    const score = calculateComplianceScore(result);
    
    return {
      violationCount,
      passCount: axeResults.passes.length,
      incompleteCount: axeResults.incomplete.length,
      inapplicableCount: axeResults.inapplicable.length,
      ...violationSummary,
      score,
    } as ScanSummary;
  }

  private calculateWCAGCompliance(
    violations: AccessibilityViolation[],
    passes: AxeResult[],
    wcagLevel: WCAGLevel,
    score: number
  ): WCAGCompliance {
    const criteriaMap = new Map<string, CriteriaResult>();
    
    // Initialize critical criteria
    CRITICAL_WCAG_CRITERIA.forEach(criterion => {
      criteriaMap.set(criterion, {
        criterion,
        level: this.getCriterionLevel(criterion),
        passed: true,
        violations: [],
      });
    });
    
    // Process violations
    violations.forEach(violation => {
      violation.wcagCriteria?.forEach(criterion => {
        if (!criteriaMap.has(criterion)) {
          criteriaMap.set(criterion, {
            criterion,
            level: this.getCriterionLevel(criterion),
            passed: false,
            violations: [violation.id],
          });
        } else {
          const result = criteriaMap.get(criterion)!;
          result.passed = false;
          result.violations.push(violation.id);
        }
      });
    });
    
    const criteriaResults = Array.from(criteriaMap.values());
    const isCompliant = isCompliantWithLevel(score, wcagLevel.level);
    
    return {
      level: wcagLevel,
      isCompliant,
      criteriaResults,
      overallScore: score,
    };
  }

  private mapToWCAGCriteria(tags: string[]): string[] {
    const criteriaMap: Record<string, string[]> = {
      'wcag111': ['1.1.1'],
      'wcag131': ['1.3.1'],
      'wcag141': ['1.4.1'],
      'wcag143': ['1.4.3'],
      'wcag211': ['2.1.1'],
      'wcag212': ['2.1.2'],
      'wcag241': ['2.4.1'],
      'wcag242': ['2.4.2'],
      'wcag243': ['2.4.3'],
      'wcag244': ['2.4.4'],
      'wcag311': ['3.1.1'],
      'wcag331': ['3.3.1'],
      'wcag332': ['3.3.2'],
      'wcag411': ['4.1.1'],
      'wcag412': ['4.1.2'],
    };
    
    const criteria = new Set<string>();
    tags.forEach(tag => {
      const mapped = criteriaMap[tag];
      if (mapped) {
        mapped.forEach(c => criteria.add(c));
      }
    });
    
    return Array.from(criteria);
  }

  private getCriterionLevel(criterion: string): 'A' | 'AA' | 'AAA' {
    const levelMap: Record<string, 'A' | 'AA' | 'AAA'> = {
      '1.1.1': 'A',
      '1.3.1': 'A',
      '1.4.1': 'A',
      '1.4.3': 'AA',
      '2.1.1': 'A',
      '2.1.2': 'A',
      '2.4.1': 'A',
      '2.4.2': 'A',
      '2.4.3': 'A',
      '2.4.4': 'A',
      '3.1.1': 'A',
      '3.3.1': 'A',
      '3.3.2': 'A',
      '4.1.1': 'A',
      '4.1.2': 'A',
    };
    
    return levelMap[criterion] || 'A';
  }

  async scanMultiplePages(
    urls: string[],
    baseOptions: Omit<AccessibilityScanOptions, 'url'>
  ): Promise<AccessibilityScanResult[]> {
    const results: AccessibilityScanResult[] = [];
    
    for (const url of urls) {
      try {
        const result = await this.scan({ ...baseOptions, url });
        results.push(result);
      } catch (error) {
        this.logger.error(`Failed to scan ${url}:`, error);
      }
    }
    
    return results;
  }
}