import * as plugins from '../../plugins.js';
import { logger } from '../../logger.js';
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
// MtaService reference removed
import type { Email } from '../core/classes.email.js';
import type { IDnsVerificationResult } from '../routing/classes.dnsmanager.js';

/**
 * DMARC policy types
 */
export enum DmarcPolicy {
  NONE = 'none',
  QUARANTINE = 'quarantine',
  REJECT = 'reject'
}

/**
 * DMARC alignment modes
 */
export enum DmarcAlignment {
  RELAXED = 'r',
  STRICT = 's'
}

/**
 * DMARC record fields
 */
export interface DmarcRecord {
  // Required fields
  version: string;
  policy: DmarcPolicy;
  
  // Optional fields
  subdomainPolicy?: DmarcPolicy;
  pct?: number;
  adkim?: DmarcAlignment;
  aspf?: DmarcAlignment;
  reportInterval?: number;
  failureOptions?: string;
  reportUriAggregate?: string[];
  reportUriForensic?: string[];
}

/**
 * DMARC verification result
 */
export interface DmarcResult {
  hasDmarc: boolean;
  record?: DmarcRecord;
  spfDomainAligned: boolean;
  dkimDomainAligned: boolean;
  spfPassed: boolean;
  dkimPassed: boolean;
  policyEvaluated: DmarcPolicy;
  actualPolicy: DmarcPolicy;
  appliedPercentage: number;
  action: 'pass' | 'quarantine' | 'reject';
  details: string;
  error?: string;
}

/**
 * Class for verifying and enforcing DMARC policies
 */
export class DmarcVerifier {
  // DNS Manager reference for verifying records
  private dnsManager?: any;
  
  constructor(dnsManager?: any) {
    this.dnsManager = dnsManager;
  }
  
  /**
   * Parse a DMARC record from a TXT record string
   * @param record DMARC TXT record string
   * @returns Parsed DMARC record or null if invalid
   */
  public parseDmarcRecord(record: string): DmarcRecord | null {
    if (!record.startsWith('v=DMARC1')) {
      return null;
    }
    
    try {
      // Initialize record with default values
      const dmarcRecord: DmarcRecord = {
        version: 'DMARC1',
        policy: DmarcPolicy.NONE,
        pct: 100,
        adkim: DmarcAlignment.RELAXED,
        aspf: DmarcAlignment.RELAXED
      };
      
      // Split the record into tag/value pairs
      const parts = record.split(';').map(part => part.trim());
      
      for (const part of parts) {
        if (!part || !part.includes('=')) continue;
        
        const [tag, value] = part.split('=').map(p => p.trim());
        
        // Process based on tag
        switch (tag.toLowerCase()) {
          case 'v':
            dmarcRecord.version = value;
            break;
          case 'p':
            dmarcRecord.policy = value as DmarcPolicy;
            break;
          case 'sp':
            dmarcRecord.subdomainPolicy = value as DmarcPolicy;
            break;
          case 'pct':
            const pctValue = parseInt(value, 10);
            if (!isNaN(pctValue) && pctValue >= 0 && pctValue <= 100) {
              dmarcRecord.pct = pctValue;
            }
            break;
          case 'adkim':
            dmarcRecord.adkim = value as DmarcAlignment;
            break;
          case 'aspf':
            dmarcRecord.aspf = value as DmarcAlignment;
            break;
          case 'ri':
            const interval = parseInt(value, 10);
            if (!isNaN(interval) && interval > 0) {
              dmarcRecord.reportInterval = interval;
            }
            break;
          case 'fo':
            dmarcRecord.failureOptions = value;
            break;
          case 'rua':
            dmarcRecord.reportUriAggregate = value.split(',').map(uri => {
              if (uri.startsWith('mailto:')) {
                return uri.substring(7).trim();
              }
              return uri.trim();
            });
            break;
          case 'ruf':
            dmarcRecord.reportUriForensic = value.split(',').map(uri => {
              if (uri.startsWith('mailto:')) {
                return uri.substring(7).trim();
              }
              return uri.trim();
            });
            break;
        }
      }
      
      // Ensure subdomain policy is set if not explicitly provided
      if (!dmarcRecord.subdomainPolicy) {
        dmarcRecord.subdomainPolicy = dmarcRecord.policy;
      }
      
      return dmarcRecord;
    } catch (error) {
      logger.log('error', `Error parsing DMARC record: ${error.message}`, {
        record,
        error: error.message
      });
      return null;
    }
  }
  
  /**
   * Check if domains are aligned according to DMARC policy
   * @param headerDomain Domain from header (From)
   * @param authDomain Domain from authentication (SPF, DKIM)
   * @param alignment Alignment mode
   * @returns Whether the domains are aligned
   */
  private isDomainAligned(
    headerDomain: string,
    authDomain: string,
    alignment: DmarcAlignment
  ): boolean {
    if (!headerDomain || !authDomain) {
      return false;
    }
    
    // For strict alignment, domains must match exactly
    if (alignment === DmarcAlignment.STRICT) {
      return headerDomain.toLowerCase() === authDomain.toLowerCase();
    }
    
    // For relaxed alignment, the authenticated domain must be a subdomain of the header domain
    // or the same as the header domain
    const headerParts = headerDomain.toLowerCase().split('.');
    const authParts = authDomain.toLowerCase().split('.');
    
    // Ensures we have at least two parts (domain and TLD)
    if (headerParts.length < 2 || authParts.length < 2) {
      return false;
    }
    
    // Get organizational domain (last two parts)
    const headerOrgDomain = headerParts.slice(-2).join('.');
    const authOrgDomain = authParts.slice(-2).join('.');
    
    return headerOrgDomain === authOrgDomain;
  }
  
  /**
   * Extract domain from an email address
   * @param email Email address
   * @returns Domain part of the email
   */
  private getDomainFromEmail(email: string): string {
    if (!email) return '';
    
    // Handle name + email format: "John Doe <john@example.com>"
    const matches = email.match(/<([^>]+)>/);
    const address = matches ? matches[1] : email;
    
    const parts = address.split('@');
    return parts.length > 1 ? parts[1] : '';
  }
  
  /**
   * Check if DMARC verification should be applied based on percentage
   * @param record DMARC record
   * @returns Whether DMARC verification should be applied
   */
  private shouldApplyDmarc(record: DmarcRecord): boolean {
    if (record.pct === undefined || record.pct === 100) {
      return true;
    }
    
    // Apply DMARC randomly based on percentage
    const random = Math.floor(Math.random() * 100) + 1;
    return random <= record.pct;
  }
  
  /**
   * Determine the action to take based on DMARC policy
   * @param policy DMARC policy
   * @returns Action to take
   */
  private determineAction(policy: DmarcPolicy): 'pass' | 'quarantine' | 'reject' {
    switch (policy) {
      case DmarcPolicy.REJECT:
        return 'reject';
      case DmarcPolicy.QUARANTINE:
        return 'quarantine';
      case DmarcPolicy.NONE:
      default:
        return 'pass';
    }
  }
  
  /**
   * Verify DMARC for an incoming email
   * @param email Email to verify
   * @param spfResult SPF verification result
   * @param dkimResult DKIM verification result
   * @returns DMARC verification result
   */
  public async verify(
    email: Email,
    spfResult: { domain: string; result: boolean },
    dkimResult: { domain: string; result: boolean }
  ): Promise<DmarcResult> {
    const securityLogger = SecurityLogger.getInstance();
    
    // Initialize result
    const result: DmarcResult = {
      hasDmarc: false,
      spfDomainAligned: false,
      dkimDomainAligned: false,
      spfPassed: spfResult.result,
      dkimPassed: dkimResult.result,
      policyEvaluated: DmarcPolicy.NONE,
      actualPolicy: DmarcPolicy.NONE,
      appliedPercentage: 100,
      action: 'pass',
      details: 'DMARC not configured'
    };
    
    try {
      // Extract From domain
      const fromHeader = email.getFromEmail();
      const fromDomain = this.getDomainFromEmail(fromHeader);
      
      if (!fromDomain) {
        result.error = 'Invalid From domain';
        return result;
      }
      
      // Check alignment
      result.spfDomainAligned = this.isDomainAligned(
        fromDomain,
        spfResult.domain,
        DmarcAlignment.RELAXED
      );
      
      result.dkimDomainAligned = this.isDomainAligned(
        fromDomain,
        dkimResult.domain,
        DmarcAlignment.RELAXED
      );
      
      // Lookup DMARC record
      const dmarcVerificationResult = this.dnsManager ? 
        await this.dnsManager.verifyDmarcRecord(fromDomain) :
        { found: false, valid: false, error: 'DNS Manager not available' };
      
      // If DMARC record exists and is valid
      if (dmarcVerificationResult.found && dmarcVerificationResult.valid) {
        result.hasDmarc = true;
        
        // Parse DMARC record
        const parsedRecord = this.parseDmarcRecord(dmarcVerificationResult.value);
        
        if (parsedRecord) {
          result.record = parsedRecord;
          result.actualPolicy = parsedRecord.policy;
          result.appliedPercentage = parsedRecord.pct || 100;
          
          // Override alignment modes if specified in record
          if (parsedRecord.adkim) {
            result.dkimDomainAligned = this.isDomainAligned(
              fromDomain,
              dkimResult.domain,
              parsedRecord.adkim
            );
          }
          
          if (parsedRecord.aspf) {
            result.spfDomainAligned = this.isDomainAligned(
              fromDomain,
              spfResult.domain,
              parsedRecord.aspf
            );
          }
          
          // Determine DMARC compliance
          const spfAligned = result.spfPassed && result.spfDomainAligned;
          const dkimAligned = result.dkimPassed && result.dkimDomainAligned;
          
          // Email passes DMARC if either SPF or DKIM passes with alignment
          const dmarcPass = spfAligned || dkimAligned;
          
          // Use record percentage to determine if policy should be applied
          const applyPolicy = this.shouldApplyDmarc(parsedRecord);
          
          if (!dmarcPass) {
            // DMARC failed, apply policy
            result.policyEvaluated = applyPolicy ? parsedRecord.policy : DmarcPolicy.NONE;
            result.action = this.determineAction(result.policyEvaluated);
            result.details = `DMARC failed: SPF aligned=${spfAligned}, DKIM aligned=${dkimAligned}, policy=${result.policyEvaluated}`;
          } else {
            result.policyEvaluated = DmarcPolicy.NONE;
            result.action = 'pass';
            result.details = `DMARC passed: SPF aligned=${spfAligned}, DKIM aligned=${dkimAligned}`;
          }
        } else {
          result.error = 'Invalid DMARC record format';
          result.details = 'DMARC record invalid';
        }
      } else {
        // No DMARC record found or invalid
        result.details = dmarcVerificationResult.error || 'No DMARC record found';
      }
      
      // Log the DMARC verification
      securityLogger.logEvent({
        level: result.action === 'pass' ? SecurityLogLevel.INFO : SecurityLogLevel.WARN,
        type: SecurityEventType.DMARC,
        message: result.details,
        domain: fromDomain,
        details: {
          fromDomain,
          spfDomain: spfResult.domain,
          dkimDomain: dkimResult.domain,
          spfPassed: result.spfPassed,
          dkimPassed: result.dkimPassed,
          spfAligned: result.spfDomainAligned,
          dkimAligned: result.dkimDomainAligned,
          dmarcPolicy: result.policyEvaluated,
          action: result.action
        },
        success: result.action === 'pass'
      });
      
      return result;
    } catch (error) {
      logger.log('error', `Error verifying DMARC: ${error.message}`, {
        error: error.message,
        emailId: email.getMessageId()
      });
      
      result.error = `DMARC verification error: ${error.message}`;
      
      // Log error
      securityLogger.logEvent({
        level: SecurityLogLevel.ERROR,
        type: SecurityEventType.DMARC,
        message: `DMARC verification failed with error`,
        details: {
          error: error.message,
          emailId: email.getMessageId()
        },
        success: false
      });
      
      return result;
    }
  }
  
  /**
   * Apply DMARC policy to an email
   * @param email Email to apply policy to
   * @param dmarcResult DMARC verification result
   * @returns Whether the email should be accepted
   */
  public applyPolicy(email: Email, dmarcResult: DmarcResult): boolean {
    // Apply action based on DMARC verification result
    switch (dmarcResult.action) {
      case 'reject':
        // Reject the email
        email.mightBeSpam = true;
        logger.log('warn', `Email rejected due to DMARC policy: ${dmarcResult.details}`, {
          emailId: email.getMessageId(),
          from: email.getFromEmail(),
          subject: email.subject
        });
        return false;
        
      case 'quarantine':
        // Quarantine the email (mark as spam)
        email.mightBeSpam = true;
        
        // Add spam header
        if (!email.headers['X-Spam-Flag']) {
          email.headers['X-Spam-Flag'] = 'YES';
        }
        
        // Add DMARC reason header
        email.headers['X-DMARC-Result'] = dmarcResult.details;
        
        logger.log('warn', `Email quarantined due to DMARC policy: ${dmarcResult.details}`, {
          emailId: email.getMessageId(),
          from: email.getFromEmail(),
          subject: email.subject
        });
        return true;
        
      case 'pass':
      default:
        // Accept the email
        // Add DMARC result header for information
        email.headers['X-DMARC-Result'] = dmarcResult.details;
        return true;
    }
  }
  
  /**
   * End-to-end DMARC verification and policy application
   * This method should be called after SPF and DKIM verification
   * @param email Email to verify
   * @param spfResult SPF verification result
   * @param dkimResult DKIM verification result
   * @returns Whether the email should be accepted
   */
  public async verifyAndApply(
    email: Email,
    spfResult: { domain: string; result: boolean },
    dkimResult: { domain: string; result: boolean }
  ): Promise<boolean> {
    // Verify DMARC
    const dmarcResult = await this.verify(email, spfResult, dkimResult);
    
    // Apply DMARC policy
    return this.applyPolicy(email, dmarcResult);
  }
}