import { chromium, Browser, BrowserContext, Page } from 'playwright';
import AxeBuilder from '@axe-core/playwright';
import type { AxeResults } from 'axe-core';
import type { 
  AccessibilityScanOptions, 
  ViewportSize,
  AuthenticationConfig 
} from '@wcag-ada/shared';
import { getWCAGTags, RESOURCE_BLOCK_PATTERNS } from '@wcag-ada/shared';
import { getScannerConfig, type ScannerConfig } from '@wcag-ada/config';

export interface AccessibilityBrowserOptions {
  headless?: boolean;
  scanTimeout?: number;
  defaultViewport?: ViewportSize;
  blockResources?: boolean;
}

export class AccessibilityBrowser {
  private browser?: Browser;
  private context?: BrowserContext;
  private logger: any;
  private config: ScannerConfig;
  private options: AccessibilityBrowserOptions;
  private initialized = false;

  constructor(logger?: any, options?: AccessibilityBrowserOptions) {
    this.logger = logger || console;
    this.config = getScannerConfig();
    
    // Merge config with provided options
    this.options = {
      headless: options?.headless ?? this.config.headless,
      scanTimeout: options?.scanTimeout ?? this.config.timeout,
      defaultViewport: options?.defaultViewport ?? this.config.viewport,
      blockResources: options?.blockResources ?? this.config.blockResources,
    };
  }

  async initialize(): Promise<void> {
    if (this.initialized) return;

    this.browser = await chromium.launch({
      headless: this.options.headless,
      args: this.config.browsers.chromium.args,
    });

    this.context = await this.browser.newContext({
      viewport: this.options.defaultViewport,
    });

    // Block resources for faster scanning if enabled
    if (this.options.blockResources) {
      await this.context.route('**/*', (route) => {
        const url = route.request().url();
        const shouldBlock = RESOURCE_BLOCK_PATTERNS.some(pattern => {
          if (pattern.includes('*')) {
            const regex = new RegExp(pattern.replace('*', '.*'));
            return regex.test(url);
          }
          return url.includes(pattern);
        });

        if (shouldBlock) {
          route.abort();
        } else {
          route.continue();
        }
      });
    }

    this.initialized = true;
  }

  async close(): Promise<void> {
    if (this.context) await this.context.close();
    if (this.browser) await this.browser.close();
    this.initialized = false;
  }

  async scanPage(options: AccessibilityScanOptions): Promise<{
    page: Page;
    axeResults: AxeResults;
    html: string;
  }> {
    if (!this.context) throw new Error('Browser not initialized');

    const { url, viewport, authenticate, wcagLevel, excludeSelectors, waitForSelector } = options;
    
    const page = await this.context.newPage();
    
    try {
      // Set viewport if different from default
      if (viewport) {
        await page.setViewportSize({
          width: viewport.width,
          height: viewport.height,
        });
      }
      
      // Handle authentication if needed
      if (authenticate) {
        await this.handleAuthentication(page, authenticate);
      }
      
      // Navigate to URL
      await page.goto(url, {
        waitUntil: 'networkidle',
        timeout: options.timeout || this.config.pageLoadTimeout,
      });
      
      // Wait for specific selector if provided
      if (waitForSelector) {
        await page.waitForSelector(waitForSelector, {
          timeout: 10000,
        });
      }
      
      // Additional wait for dynamic content
      await page.waitForTimeout(2000);
      
      // Get page HTML
      const html = await page.content();
      
      // Configure axe
      const axeBuilder = new AxeBuilder({ page });
      
      // Set WCAG tags
      if (wcagLevel) {
        const tags = getWCAGTags(wcagLevel);
        axeBuilder.withTags(tags);
      }
      
      // Exclude selectors if provided
      if (excludeSelectors && excludeSelectors.length > 0) {
        excludeSelectors.forEach(selector => {
          axeBuilder.exclude(selector);
        });
      }
      
      // Set options
      axeBuilder.options({
        resultTypes: this.config.axe.resultTypes,
        elementRef: true,
        runOnly: {
          type: 'tag',
          values: wcagLevel ? getWCAGTags(wcagLevel) : this.config.axe.tags,
        },
      });
      
      // Run accessibility scan
      const axeResults = await Promise.race([
        axeBuilder.analyze(),
        new Promise<never>((_, reject) => 
          setTimeout(() => reject(new Error('Scan timeout')), this.options.scanTimeout!)
        ),
      ]);
      
      return {
        page,
        axeResults,
        html,
      };
    } catch (error) {
      await page.close();
      throw error;
    }
  }

  private async handleAuthentication(page: Page, auth: AuthenticationConfig): Promise<void> {
    switch (auth.type) {
      case 'basic':
        if (auth.credentials?.username && auth.credentials?.password) {
          await page.context().setHTTPCredentials({
            username: auth.credentials.username,
            password: auth.credentials.password,
          });
        }
        break;
        
      case 'form':
        if (auth.loginUrl) {
          await page.goto(auth.loginUrl);
          
          if (auth.selectors?.username && auth.credentials?.username) {
            await page.fill(auth.selectors.username, auth.credentials.username);
          }
          
          if (auth.selectors?.password && auth.credentials?.password) {
            await page.fill(auth.selectors.password, auth.credentials.password);
          }
          
          if (auth.selectors?.submit) {
            await page.click(auth.selectors.submit);
            await page.waitForNavigation({ waitUntil: 'networkidle' });
          }
        }
        break;
        
      case 'custom':
        if (auth.customScript) {
          await page.evaluate(auth.customScript);
        }
        break;
    }
  }

  async captureViolationScreenshots(
    page: Page, 
    violations: AxeResults['violations']
  ): Promise<Map<string, string>> {
    const screenshots = new Map<string, string>();
    
    for (const violation of violations) {
      for (let i = 0; i < violation.nodes.length; i++) {
        const node = violation.nodes[i];
        if (node.target && node.target.length > 0) {
          try {
            const selector = node.target[0];
            const element = await page.$(selector);
            
            if (element) {
              const screenshot = await element.screenshot({
                type: 'png',
              });
              
              const key = `${violation.id}_${i}`;
              screenshots.set(key, screenshot.toString('base64'));
            }
          } catch (error) {
            this.logger?.warn(`Failed to capture screenshot for ${violation.id}:`, error);
          }
        }
      }
    }
    
    return screenshots;
  }

  async evaluateCustomRules(page: Page, customRules: any[]): Promise<any[]> {
    const results = [];
    
    for (const rule of customRules) {
      try {
        const elements = await page.$$(rule.selector);
        const violations = [];
        
        for (const element of elements) {
          const passes = await page.evaluate(
            (el, validatorStr) => {
              const validator = new Function('element', `return (${validatorStr})(element)`);
              return validator(el);
            },
            element,
            rule.validator.toString()
          );
          
          if (!passes) {
            const html = await element.evaluate(el => el.outerHTML);
            violations.push({
              html,
              target: [rule.selector],
            });
          }
        }
        
        if (violations.length > 0) {
          results.push({
            id: rule.id,
            description: rule.description,
            help: rule.help,
            helpUrl: rule.helpUrl,
            impact: rule.severity,
            tags: rule.tags,
            nodes: violations,
          });
        }
      } catch (error) {
        this.logger?.error(`Failed to evaluate custom rule ${rule.id}:`, error);
      }
    }
    
    return results;
  }
}