/**
 * Robotic Classifier
 * DB-driven keyword classification engine for ticket triage.
 * Handles ~90% of classification deterministically without AI.
 */

import { postgresClient } from './postgres-client';
import {
  ClassificationRule,
  ClassificationResult,
  ClassificationStepResult,
  ConfidenceLevel,
  RuleType,
  TicketData,
} from '../types/workflow';

// Cache TTL for classification rules (5 minutes)
const RULES_CACHE_TTL_MS = 5 * 60 * 1000;

export class RoboticClassifier {
  private rulesCache: Map<RuleType, ClassificationRule[]> = new Map();
  private cacheLoadedAt: number = 0;

  /**
   * Load classification rules from DB, grouped by rule_type.
   * Cached for RULES_CACHE_TTL_MS to avoid repeated DB queries.
   */
  async loadRules(forceRefresh = false): Promise<Map<RuleType, ClassificationRule[]>> {
    const now = Date.now();
    if (!forceRefresh && this.rulesCache.size > 0 && now - this.cacheLoadedAt < RULES_CACHE_TTL_MS) {
      return this.rulesCache;
    }

    const result = await postgresClient.query<ClassificationRule>(
      `SELECT * FROM classification_rules WHERE is_active = true ORDER BY rule_type, sort_order`
    );

    this.rulesCache.clear();
    for (const rule of result.rows) {
      const existing = this.rulesCache.get(rule.rule_type) || [];
      existing.push(rule);
      this.rulesCache.set(rule.rule_type, existing);
    }

    this.cacheLoadedAt = now;
    console.log(`[CLASSIFIER] Loaded ${result.rows.length} classification rules`);
    return this.rulesCache;
  }

  /**
   * Run full classification pipeline on a ticket.
   */
  async classify(ticket: TicketData): Promise<ClassificationResult> {
    await this.loadRules();

    const branch = await this.classifyByType('branch_routing', ticket);
    const ticketType = await this.classifyByType('ticket_type', ticket);
    const issueClassification = await this.classifyByType('issue_classification', ticket);

    // Priority classification can use ticket_type result as context
    const priorityTicket = { ...ticket };
    if (ticketType?.value != null) {
      priorityTicket.ticket_type = ticketType.value;
    }
    const priority = await this.classifyByType('priority', priorityTicket);

    // Queue routing can use priority result as context
    const queueTicket = { ...priorityTicket };
    if (priority?.value != null) {
      queueTicket.priority = priority.value;
    }
    const queue = await this.classifyByType('queue_routing', queueTicket);

    // Determine overall confidence and whether AI is needed
    const stepResults = [branch, ticketType, issueClassification, priority, queue];
    const { overallConfidence, needsAi, aiReasons } = this.assessConfidence(
      stepResults,
      ticket
    );

    return {
      branch: branch || this.defaultBranch(),
      ticket_type: ticketType,
      issue_classification: issueClassification,
      priority: priority,
      queue: queue,
      overall_confidence: overallConfidence,
      needs_ai: needsAi,
      ai_reasons: aiReasons,
    };
  }

  /**
   * Run classification for a specific rule type.
   */
  private async classifyByType(
    ruleType: RuleType,
    ticket: TicketData
  ): Promise<ClassificationStepResult | null> {
    const rules = this.rulesCache.get(ruleType) || [];

    for (const rule of rules) {
      if (this.evaluateRule(rule, ticket)) {
        return {
          field: rule.result_field,
          value: rule.result_value,
          field_2: rule.result_field_2 || undefined,
          value_2: rule.result_value_2 || undefined,
          confidence: rule.confidence,
          matched_rule_id: rule.id,
          matched_rule_name: rule.name,
          method: 'robotic',
        };
      }
    }

    return null;
  }

  /**
   * Evaluate a single classification rule against ticket data.
   */
  private evaluateRule(rule: ClassificationRule, ticket: TicketData): boolean {
    const fieldValue = this.getFieldValue(rule.match_field, ticket);

    if (fieldValue === null || fieldValue === undefined) {
      return false;
    }

    return this.evaluateMatch(
      rule.match_operator,
      fieldValue,
      rule.match_value,
      rule.match_case_sensitive
    );
  }

  /**
   * Get the value of a field from the ticket data.
   * For 'title_or_description', returns a combined check target.
   */
  private getFieldValue(
    matchField: string,
    ticket: TicketData
  ): string | number | null {
    switch (matchField) {
      case 'title':
        return ticket.title || null;
      case 'description':
        return ticket.description || null;
      case 'title_or_description':
        // Return combined text for pattern matching
        return [ticket.title, ticket.description].filter(Boolean).join(' ') || null;
      case 'ticket_category':
        return ticket.ticket_category;
      case 'ticket_type':
        return ticket.ticket_type;
      case 'priority':
        return ticket.priority;
      case 'policy_name':
        return ticket.policy_name || null;
      case 'device_name':
        return ticket.device_name || null;
      case 'creator_resource_id':
        return ticket.creator_resource_id;
      case 'person_id':
        return ticket.person_id;
      case 'company_id':
        return ticket.company_id;
      default:
        return null;
    }
  }

  /**
   * Evaluate a match operation.
   */
  private evaluateMatch(
    operator: string,
    fieldValue: string | number,
    matchValue: any,
    caseSensitive: boolean
  ): boolean {
    switch (operator) {
      case 'contains':
        return this.evaluateContains(fieldValue, matchValue, caseSensitive);
      case 'starts_with':
        return this.evaluateStartsWith(fieldValue, matchValue, caseSensitive);
      case 'regex':
        return this.evaluateRegex(fieldValue, matchValue, caseSensitive);
      case 'equals':
        return this.evaluateEquals(fieldValue, matchValue);
      case 'in':
        return this.evaluateIn(fieldValue, matchValue);
      case 'not_in':
        return !this.evaluateIn(fieldValue, matchValue);
      default:
        return false;
    }
  }

  /**
   * Contains: check if field contains any of the match values.
   * matchValue can be a string or array of strings.
   */
  private evaluateContains(
    fieldValue: string | number,
    matchValue: any,
    caseSensitive: boolean
  ): boolean {
    const text = String(fieldValue);
    const searchText = caseSensitive ? text : text.toLowerCase();
    const patterns = Array.isArray(matchValue) ? matchValue : [matchValue];

    return patterns.some((pattern: string) => {
      const searchPattern = caseSensitive ? String(pattern) : String(pattern).toLowerCase();
      return searchText.includes(searchPattern);
    });
  }

  /**
   * Starts with: check if field starts with the match value.
   */
  private evaluateStartsWith(
    fieldValue: string | number,
    matchValue: any,
    caseSensitive: boolean
  ): boolean {
    const text = String(fieldValue);
    const searchText = caseSensitive ? text : text.toLowerCase();
    const patterns = Array.isArray(matchValue) ? matchValue : [matchValue];

    return patterns.some((pattern: string) => {
      const searchPattern = caseSensitive ? String(pattern) : String(pattern).toLowerCase();
      return searchText.startsWith(searchPattern);
    });
  }

  /**
   * Regex: evaluate a regex pattern against the field value.
   */
  private evaluateRegex(
    fieldValue: string | number,
    matchValue: any,
    caseSensitive: boolean
  ): boolean {
    const text = String(fieldValue);
    const pattern = String(matchValue);
    try {
      const flags = caseSensitive ? '' : 'i';
      const regex = new RegExp(pattern, flags);
      return regex.test(text);
    } catch {
      console.error(`[CLASSIFIER] Invalid regex pattern: ${pattern}`);
      return false;
    }
  }

  /**
   * Equals: exact match (handles numbers and strings).
   */
  private evaluateEquals(
    fieldValue: string | number,
    matchValue: any
  ): boolean {
    // Compare as numbers if both are numeric
    const numField = Number(fieldValue);
    const numMatch = Number(matchValue);
    if (!isNaN(numField) && !isNaN(numMatch)) {
      return numField === numMatch;
    }
    return String(fieldValue).toLowerCase() === String(matchValue).toLowerCase();
  }

  /**
   * In: check if field value is in the match value array.
   */
  private evaluateIn(
    fieldValue: string | number,
    matchValue: any
  ): boolean {
    const values = Array.isArray(matchValue) ? matchValue : [matchValue];
    const numField = Number(fieldValue);

    return values.some((v: any) => {
      const numV = Number(v);
      if (!isNaN(numField) && !isNaN(numV)) {
        return numField === numV;
      }
      return String(fieldValue).toLowerCase() === String(v).toLowerCase();
    });
  }

  /**
   * Default branch when no branch routing rule matches.
   */
  private defaultBranch(): ClassificationStepResult {
    return {
      field: 'branch',
      value: 'service_desk',
      confidence: 'high',
      matched_rule_id: null,
      matched_rule_name: 'Default (service_desk)',
      method: 'robotic',
    };
  }

  /**
   * Assess overall confidence and determine if AI enhancement is needed.
   */
  private assessConfidence(
    results: (ClassificationStepResult | null)[],
    ticket: TicketData
  ): { overallConfidence: ConfidenceLevel; needsAi: boolean; aiReasons: string[] } {
    const aiReasons: string[] = [];

    // Check for missing classifications
    const [branch, ticketType, issueClass, priority, queue] = results;

    if (!ticketType) {
      aiReasons.push('No ticket type classification matched');
    }
    if (!issueClass) {
      aiReasons.push('No issue type classification matched');
    }
    if (!priority) {
      aiReasons.push('No priority classification matched');
    }

    // Check for low confidence results
    for (const result of results) {
      if (result && result.confidence === 'low') {
        aiReasons.push(`Low confidence on ${result.field}: ${result.matched_rule_name}`);
      }
    }

    // Check if title looks like it needs cleanup (email subject, too long, garbled)
    if (ticket.title) {
      if (ticket.title.length > 150) {
        aiReasons.push('Title is very long (possible email subject)');
      }
      if (/^(re:|fw:|fwd:)/i.test(ticket.title)) {
        aiReasons.push('Title is a forwarded/replied email subject');
      }
    }

    // Determine overall confidence
    let overallConfidence: ConfidenceLevel;
    if (aiReasons.length === 0) {
      overallConfidence = 'high';
    } else if (aiReasons.length <= 2) {
      overallConfidence = 'medium';
    } else {
      overallConfidence = 'low';
    }

    return {
      overallConfidence,
      needsAi: aiReasons.length > 0,
      aiReasons,
    };
  }

  /**
   * Force refresh the rules cache.
   */
  async refreshCache(): Promise<void> {
    await this.loadRules(true);
  }
}

// Export singleton instance
export const roboticClassifier = new RoboticClassifier();
