import * as plugins from '../plugins.js';

/**
 * Domain group configuration for applying consistent rules across related domains
 */
export interface IDomainGroup {
  /** Unique identifier for the domain group */
  id: string;
  /** Human-readable name for the domain group */
  name: string;
  /** List of domains in this group */
  domains: string[];
  /** Priority for this domain group (higher takes precedence) */
  priority?: number;
  /** Description of this domain group */
  description?: string;
}

/**
 * Domain pattern with wildcard support for matching domains
 */
export interface IDomainPattern {
  /** The domain pattern, e.g. "example.com" or "*.example.com" */
  pattern: string;
  /** Whether this is an exact match or wildcard pattern */
  isWildcard: boolean;
}

/**
 * Email routing rule for determining how to handle emails for specific domains
 */
export interface IEmailRoutingRule {
  /** Unique identifier for this rule */
  id: string;
  /** Human-readable name for this rule */
  name: string;
  /** Source domain patterns to match (from address) */
  sourceDomains?: IDomainPattern[];
  /** Destination domain patterns to match (to address) */
  destinationDomains?: IDomainPattern[];
  /** Domain groups this rule applies to */
  domainGroups?: string[];
  /** Priority of this rule (higher takes precedence) */
  priority: number;
  /** Action to take when rule matches */
  action: 'route' | 'block' | 'tag' | 'filter';
  /** Target server for routing */
  targetServer?: string;
  /** Target port for routing */
  targetPort?: number;
  /** Whether to use TLS when routing */
  useTls?: boolean;
  /** Authentication details for routing */
  auth?: {
    /** Username for authentication */
    username?: string;
    /** Password for authentication */
    password?: string;
    /** Authentication type */
    type?: 'PLAIN' | 'LOGIN' | 'OAUTH2';
  };
  /** Headers to add or modify when rule matches */
  headers?: {
    /** Header name */
    name: string;
    /** Header value */
    value: string;
    /** Whether to append to existing header or replace */
    append?: boolean;
  }[];
  /** Whether this rule is enabled */
  enabled: boolean;
}

/**
 * Configuration for email domain-based routing
 */
export interface IEmailDomainRoutingConfig {
  /** Whether domain-based routing is enabled */
  enabled: boolean;
  /** Routing rules list */
  rules: IEmailRoutingRule[];
  /** Domain groups for organization */
  domainGroups?: IDomainGroup[];
  /** Default target server for unmatched domains */
  defaultTargetServer?: string;
  /** Default target port for unmatched domains */
  defaultTargetPort?: number;
  /** Whether to use TLS for the default route */
  defaultUseTls?: boolean;
}

/**
 * Class for managing domain-based email routing
 */
export class EmailDomainRouter {
  /** Configuration for domain-based routing */
  private config: IEmailDomainRoutingConfig;
  /** Domain groups indexed by ID */
  private domainGroups: Map<string, IDomainGroup> = new Map();
  /** Sorted rules cache for faster processing */
  private sortedRules: IEmailRoutingRule[] = [];
  /** Whether the rules need to be re-sorted */
  private rulesSortNeeded = true;

  /**
   * Create a new EmailDomainRouter
   * @param config Configuration for domain-based routing
   */
  constructor(config: IEmailDomainRoutingConfig) {
    this.config = config;
    this.initialize();
  }

  /**
   * Initialize the domain router
   */
  private initialize(): void {
    // Return early if routing is not enabled
    if (!this.config.enabled) {
      return;
    }

    // Initialize domain groups
    if (this.config.domainGroups) {
      for (const group of this.config.domainGroups) {
        this.domainGroups.set(group.id, group);
      }
    }

    // Sort rules by priority
    this.sortRules();
  }

  /**
   * Sort rules by priority (higher first)
   */
  private sortRules(): void {
    if (!this.config.rules || !this.config.enabled) {
      this.sortedRules = [];
      this.rulesSortNeeded = false;
      return;
    }

    this.sortedRules = [...this.config.rules]
      .filter(rule => rule.enabled)
      .sort((a, b) => b.priority - a.priority);

    this.rulesSortNeeded = false;
  }

  /**
   * Add a new routing rule
   * @param rule The routing rule to add
   */
  public addRule(rule: IEmailRoutingRule): void {
    if (!this.config.rules) {
      this.config.rules = [];
    }

    // Check if rule already exists
    const existingIndex = this.config.rules.findIndex(r => r.id === rule.id);
    if (existingIndex >= 0) {
      // Update existing rule
      this.config.rules[existingIndex] = rule;
    } else {
      // Add new rule
      this.config.rules.push(rule);
    }

    this.rulesSortNeeded = true;
  }

  /**
   * Remove a routing rule by ID
   * @param ruleId ID of the rule to remove
   * @returns Whether the rule was removed
   */
  public removeRule(ruleId: string): boolean {
    if (!this.config.rules) {
      return false;
    }

    const initialLength = this.config.rules.length;
    this.config.rules = this.config.rules.filter(rule => rule.id !== ruleId);
    
    if (initialLength !== this.config.rules.length) {
      this.rulesSortNeeded = true;
      return true;
    }
    
    return false;
  }

  /**
   * Add a domain group
   * @param group The domain group to add
   */
  public addDomainGroup(group: IDomainGroup): void {
    if (!this.config.domainGroups) {
      this.config.domainGroups = [];
    }

    // Check if group already exists
    const existingIndex = this.config.domainGroups.findIndex(g => g.id === group.id);
    if (existingIndex >= 0) {
      // Update existing group
      this.config.domainGroups[existingIndex] = group;
    } else {
      // Add new group
      this.config.domainGroups.push(group);
    }

    // Update domain groups map
    this.domainGroups.set(group.id, group);
  }

  /**
   * Remove a domain group by ID
   * @param groupId ID of the group to remove
   * @returns Whether the group was removed
   */
  public removeDomainGroup(groupId: string): boolean {
    if (!this.config.domainGroups) {
      return false;
    }

    const initialLength = this.config.domainGroups.length;
    this.config.domainGroups = this.config.domainGroups.filter(group => group.id !== groupId);
    
    if (initialLength !== this.config.domainGroups.length) {
      this.domainGroups.delete(groupId);
      return true;
    }
    
    return false;
  }

  /**
   * Determine routing for an email
   * @param fromDomain The sender domain
   * @param toDomain The recipient domain
   * @returns Routing decision or null if no matching rule
   */
  public getRoutingForEmail(fromDomain: string, toDomain: string): {
    targetServer: string;
    targetPort: number;
    useTls: boolean;
    auth?: {
      username?: string;
      password?: string;
      type?: 'PLAIN' | 'LOGIN' | 'OAUTH2';
    };
    headers?: {
      name: string;
      value: string;
      append?: boolean;
    }[];
  } | null {
    // Return default routing if routing is not enabled
    if (!this.config.enabled) {
      return this.getDefaultRouting();
    }

    // Sort rules if needed
    if (this.rulesSortNeeded) {
      this.sortRules();
    }

    // Normalize domains
    fromDomain = fromDomain.toLowerCase();
    toDomain = toDomain.toLowerCase();

    // Check each rule in priority order
    for (const rule of this.sortedRules) {
      if (!rule.enabled) continue;

      // Check if rule applies to this email
      if (this.ruleMatchesEmail(rule, fromDomain, toDomain)) {
        // Handle different actions
        switch (rule.action) {
          case 'route':
            // Return routing information
            return {
              targetServer: rule.targetServer || this.config.defaultTargetServer || 'localhost',
              targetPort: rule.targetPort || this.config.defaultTargetPort || 25,
              useTls: rule.useTls ?? this.config.defaultUseTls ?? false,
              auth: rule.auth,
              headers: rule.headers
            };
          case 'block':
            // Return null to indicate email should be blocked
            return null;
          case 'tag':
          case 'filter':
            // For tagging/filtering, we need to apply headers but continue checking rules
            // This is simplified for now, in a real implementation we'd aggregate headers
            continue;
        }
      }
    }

    // No rule matched, use default routing
    return this.getDefaultRouting();
  }

  /**
   * Check if a rule matches an email
   * @param rule The routing rule to check
   * @param fromDomain The sender domain
   * @param toDomain The recipient domain
   * @returns Whether the rule matches the email
   */
  private ruleMatchesEmail(rule: IEmailRoutingRule, fromDomain: string, toDomain: string): boolean {
    // Check source domains
    if (rule.sourceDomains && rule.sourceDomains.length > 0) {
      const matchesSourceDomain = rule.sourceDomains.some(
        pattern => this.domainMatchesPattern(fromDomain, pattern)
      );
      if (!matchesSourceDomain) {
        return false;
      }
    }

    // Check destination domains
    if (rule.destinationDomains && rule.destinationDomains.length > 0) {
      const matchesDestinationDomain = rule.destinationDomains.some(
        pattern => this.domainMatchesPattern(toDomain, pattern)
      );
      if (!matchesDestinationDomain) {
        return false;
      }
    }

    // Check domain groups
    if (rule.domainGroups && rule.domainGroups.length > 0) {
      // Check if either domain is in any of the specified groups
      const domainsInGroups = rule.domainGroups
        .map(groupId => this.domainGroups.get(groupId))
        .filter(Boolean)
        .some(group => 
          group.domains.includes(fromDomain) || 
          group.domains.includes(toDomain)
        );
      
      if (!domainsInGroups) {
        return false;
      }
    }

    // If we got here, all checks passed
    return true;
  }

  /**
   * Check if a domain matches a pattern
   * @param domain The domain to check
   * @param pattern The pattern to match against
   * @returns Whether the domain matches the pattern
   */
  private domainMatchesPattern(domain: string, pattern: IDomainPattern): boolean {
    domain = domain.toLowerCase();
    const patternStr = pattern.pattern.toLowerCase();

    // Exact match
    if (!pattern.isWildcard) {
      return domain === patternStr;
    }

    // Wildcard match (*.example.com)
    if (patternStr.startsWith('*.')) {
      const suffix = patternStr.substring(2);
      return domain.endsWith(suffix) && domain.length > suffix.length;
    }

    // Invalid pattern
    return false;
  }

  /**
   * Get default routing information
   * @returns Default routing or null if no default configured
   */
  private getDefaultRouting(): {
    targetServer: string;
    targetPort: number;
    useTls: boolean;
  } | null {
    if (!this.config.defaultTargetServer) {
      return null;
    }

    return {
      targetServer: this.config.defaultTargetServer,
      targetPort: this.config.defaultTargetPort || 25,
      useTls: this.config.defaultUseTls || false
    };
  }

  /**
   * Get the current configuration
   * @returns Current domain routing configuration
   */
  public getConfig(): IEmailDomainRoutingConfig {
    return this.config;
  }

  /**
   * Update the configuration
   * @param config New domain routing configuration
   */
  public updateConfig(config: IEmailDomainRoutingConfig): void {
    this.config = config;
    this.rulesSortNeeded = true;
    this.initialize();
  }

  /**
   * Enable domain routing
   */
  public enable(): void {
    this.config.enabled = true;
  }

  /**
   * Disable domain routing
   */
  public disable(): void {
    this.config.enabled = false;
  }
}