import { Injectable } from '@nestjs/common';
import { createHash } from 'crypto';
import { MonitorLogEventEntity } from './monitor-log-event.entity';
import { LogAnalysisTemplate, LogSeverity } from './log-analysis.types';

@Injectable()
export class Drain3Adapter {
  readonly mode: 'configured' | 'fallback';

  constructor() {
    this.mode = process.env.DRAIN3_API_BASE_URL?.trim()
      ? 'configured'
      : 'fallback';
  }

  async buildTemplates(logs: MonitorLogEventEntity[]) {
    // Phase 1 keeps the adapter boundary compatible with a future Drain3 API.
    // The fallback path provides local template grouping for demos and early use.
    const groups = new Map<
      string,
      {
        normalized: string;
        messages: string[];
        levels: Set<string>;
      }
    >();

    for (const log of logs) {
      const normalized = this.normalizeMessage(log.messageText);
      const key = this.hash(normalized);
      const current = groups.get(key) ?? {
        normalized,
        messages: [],
        levels: new Set<string>(),
      };
      current.messages.push(log.messageText);
      current.levels.add((log.level ?? 'INFO').toUpperCase());
      groups.set(key, current);
    }

    return Array.from(groups.entries())
      .map(([key, value]) => {
        const levels = Array.from(value.levels.values()).sort();
        const severity = this.deriveSeverity(levels, value.messages);
        return {
          templateId: key,
          templateText: value.normalized,
          sampleCount: value.messages.length,
          sampleMessages: value.messages.slice(0, 3),
          levels,
          category: this.inferCategory(value.normalized, value.messages),
          severity,
        } satisfies LogAnalysisTemplate;
      })
      .sort((left, right) => {
        if (left.severity !== right.severity) {
          return this.rankSeverity(right.severity) - this.rankSeverity(left.severity);
        }
        return right.sampleCount - left.sampleCount;
      });
  }

  private normalizeMessage(message: string) {
    return message
      .replace(/\b\d{1,3}(?:\.\d{1,3}){3}\b/g, '<IP>')
      .replace(/\b\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?\b/g, '<TIME>')
      .replace(/\b[0-9a-f]{8,}\b/gi, '<HEX>')
      .replace(/\b\d+\b/g, '<NUM>')
      .replace(/".*?"/g, '"<TEXT>"')
      .replace(/\s+/g, ' ')
      .trim();
  }

  private hash(value: string) {
    return createHash('sha1').update(value).digest('hex').slice(0, 12);
  }

  private deriveSeverity(levels: string[], messages: string[]): LogSeverity {
    const joined = messages.join('\n').toLowerCase();
    if (
      levels.includes('ERROR') ||
      /exception|fatal|panic|outofmemory|oom|deadlock|cannot find module|econnrefused|eaddrinuse/.test(
        joined,
      )
    ) {
      return 'critical';
    }
    if (
      levels.includes('WARN') ||
      /timeout|timed out|already running|skip this schedule tick|unhealthy/.test(
        joined,
      )
    ) {
      return 'warning';
    }
    return 'info';
  }

  private inferCategory(normalized: string, messages: string[]) {
    const text = `${normalized}\n${messages.join('\n')}`.toLowerCase();
    if (
      /econnrefused|unable to connect to the database|access denied|authentication|communications link failure|cjcommunicationsexception|connection reset|test sql failed|jdbc.*communications/i.test(
        text,
      )
    ) return 'database-connectivity';
    if (/eaddrinuse|address already in use|listen .* in use/i.test(text)) return 'port-conflict';
    if (/already running|skip this schedule tick|queued|stale/i.test(text)) return 'scheduler-backlog';
    if (/task .*不存在|task .*not found|巡检任务不存在/i.test(text)) return 'task-config';
    if (/exception|cannot find module|unknowndependenciesexception/i.test(text)) return 'application-runtime';
    if (/warn|warning/i.test(text)) return 'warning-noise';
    return 'general-log-pattern';
  }

  private rankSeverity(severity: LogSeverity) {
    return { info: 1, warning: 2, major: 3, critical: 4 }[severity];
  }
}
