import { Injectable } from '@nestjs/common';
import { MonitorLogEventEntity } from './monitor-log-event.entity';
import { MonitorRunEntity } from './monitor-run.entity';
import { MonitorTargetConfig } from './monitoring.types';
import { Drain3Adapter } from './drain3.adapter';
import {
  LogAnalysisIssue,
  LogAnalysisReport,
  LogAnalysisTemplate,
  LogSeverity,
} from './log-analysis.types';
import { OpenObserveLogAdapter } from './openobserve-log.adapter';

@Injectable()
export class LogAnalysisService {
  constructor(
    private readonly openobserveAdapter: OpenObserveLogAdapter,
    private readonly drain3Adapter: Drain3Adapter,
  ) {}

  async analyzeRun(
    run: MonitorRunEntity,
    config: MonitorTargetConfig,
    fallbackLogs: () => Promise<MonitorLogEventEntity[]>,
  ) {
    const recentLogs = await this.openobserveAdapter.fetchRecentLogs(
      {
        serviceKey: run.target.serviceKey,
        nodeKey: config.nodeKey,
        limit: 50,
      },
      fallbackLogs,
    );
    const logs = this.mergeLogsWithRunExcerpt(run, recentLogs);
    const templates = await this.drain3Adapter.buildTemplates(logs);
    const issues = this.buildIssues(run, templates, logs);
    const severity = this.pickSeverity(run.status, issues, templates);
    const stats = this.buildStats(logs, templates);
    const overview = this.buildOverview(run, issues, stats);

    return {
      targetType: run.target.type,
      source: {
        openobserve: this.openobserveAdapter.mode,
        drain3: this.drain3Adapter.mode,
      },
      overview,
      severity,
      templates: templates.slice(0, 8),
      issues,
      stats,
    } satisfies LogAnalysisReport;
  }

  private mergeLogsWithRunExcerpt(
    run: MonitorRunEntity,
    logs: MonitorLogEventEntity[],
  ) {
    const merged = [...logs];
    const seen = new Set(
      logs.map((item) => `${item.eventAt.toISOString()}|${item.level ?? ''}|${item.messageText}`),
    );

    for (const line of this.extractExcerptLines(run, run.logExcerpt)) {
      const key = `${line.eventAt.toISOString()}|${line.level ?? ''}|${line.messageText}`;
      if (seen.has(key)) continue;
      seen.add(key);
      merged.push(line);
    }

    return merged
      .sort((left, right) => right.eventAt.getTime() - left.eventAt.getTime())
      .slice(0, 80);
  }

  private extractExcerptLines(run: MonitorRunEntity, logExcerpt?: string | null) {
    if (!logExcerpt?.trim()) return [];

    const baseTime = runSafeTime(logExcerpt);
    const lines = logExcerpt
      .split(/\r?\n/)
      .map((item) => item.trim())
      .filter(Boolean);

    return lines.map((messageText, index) => {
      const event = new MonitorLogEventEntity();
      event.serviceKey = run.target.serviceKey;
      event.nodeKey = null;
      event.host = null;
      event.sourceType = 'manual';
      event.sourceName = 'run-log-excerpt';
      event.level = inferLevel(messageText);
      event.messageText = messageText;
      event.eventAt = new Date(baseTime.getTime() - index * 1000);
      event.metaJson = null;
      return event;
    });
  }

  private buildIssues(
    run: MonitorRunEntity,
    templates: LogAnalysisTemplate[],
    logs: MonitorLogEventEntity[],
  ) {
    const issues: LogAnalysisIssue[] = [];
    const emitted = new Set<string>();

    const pushIssue = (issue: LogAnalysisIssue) => {
      if (emitted.has(issue.category)) return;
      emitted.add(issue.category);
      issues.push(issue);
    };

    const addTemplateIssue = (
      template: LogAnalysisTemplate,
      title: string,
      possibleCauses: string[],
      recommendedActions: string[],
      confidence: 'low' | 'medium' | 'high' = 'high',
    ) => {
      pushIssue({
        id: `issue-${template.templateId}`,
        title,
        category: template.category,
        severity: template.severity,
        confidence,
        summary: `${title}，最近命中 ${template.sampleCount} 次。`,
        evidence: template.sampleMessages,
        possibleCauses,
        recommendedActions,
        templateIds: [template.templateId],
      });
    };

    for (const template of templates) {
      if (template.category === 'database-connectivity') {
        addTemplateIssue(
          template,
          '数据库连接异常',
          ['数据库未启动或监听端口不可达', '连接串、账号或认证信息错误', '应用与数据库之间存在网络或防火墙阻断'],
          ['先在目标机验证数据库端口和账号可用性', '核对 SentAI 监测目标配置与业务程序连接参数', '若持续出现拒绝连接，检查数据库实例状态与白名单'],
        );
        continue;
      }
      if (template.category === 'port-conflict') {
        addTemplateIssue(
          template,
          '服务端口冲突',
          ['已有旧进程占用了监听端口', '同机启动了重复实例', '开发/部署脚本没有正确释放旧进程'],
          ['先定位并确认当前占用该端口的进程', '清理重复实例后再重启目标服务', '把启动前端口检查加入部署或守护脚本'],
        );
        continue;
      }
      if (template.category === 'scheduler-backlog') {
        addTemplateIssue(
          template,
          '调度堆积或执行阻塞',
          ['任务执行时间超过调度周期', '某次执行未正常结束，导致后续调度持续跳过', '调度配置过密，超出当前资源容量'],
          ['检查 run 状态是否长期停留在 running/queued', '拉大 cron 周期或限制并发', '对长时间未完成的任务启用自动回收与重派'],
          'medium',
        );
        continue;
      }
      if (template.category === 'task-config') {
        addTemplateIssue(
          template,
          '监测目标或任务配置失效',
          ['任务已被删除，但调度器仍在触发旧任务', '任务 ID 与调度注册状态不一致', '配置变更后调度器未同步刷新'],
          ['先清理失效任务与残留调度项', '重新同步监测目标和调度器', '为删除动作补充调度注销校验'],
          'high',
        );
        continue;
      }
      if (template.category === 'application-runtime' && template.severity !== 'info') {
        addTemplateIssue(
          template,
          '应用运行时异常',
          ['代码依赖缺失或模块加载失败', '应用启动参数或依赖注入配置错误', '版本发布不完整或构建产物损坏'],
          ['优先查看对应异常栈和模块路径', '核对构建产物、依赖包和环境变量', '把启动自检结果也纳入 SentAI 执行记录'],
        );
      }
    }

    if (!issues.length && run.status !== 'success') {
      const evidence = logs.slice(0, 3).map((item) => item.messageText);
      pushIssue({
        id: `issue-run-${run.id}`,
        title: '监测已失败，但暂未命中明确日志模板',
        category: 'unknown-failure',
        severity: 'major',
        confidence: 'low',
        summary: '当前已有失败结果，但日志模式尚不足以自动归因。',
        evidence,
        possibleCauses: ['外部依赖瞬时异常', '采集窗口过短，关键日志未进入当前样本', '当前规则库尚未覆盖该异常模式'],
        recommendedActions: ['扩大日志采样窗口到最近 5-15 分钟', '补充该异常的样例日志进入规则库', '结合 metrics/details 与业务日志做二次判断'],
        templateIds: [],
      });
    }

    return issues.slice(0, 5);
  }

  private pickSeverity(
    runStatus: MonitorRunEntity['status'],
    issues: LogAnalysisIssue[],
    templates: LogAnalysisTemplate[],
  ): LogSeverity {
    const severities = [
      ...issues.map((item) => item.severity),
      ...templates.map((item) => item.severity),
    ];
    if (runStatus === 'failed' && !severities.length) return 'major';
    if (severities.includes('critical')) return 'critical';
    if (runStatus === 'failed' || severities.includes('major')) return 'major';
    if (runStatus === 'partial' || severities.includes('warning')) return 'warning';
    return 'info';
  }

  private buildStats(logs: MonitorLogEventEntity[], templates: LogAnalysisTemplate[]) {
    const levelOf = (value?: string | null) => (value ?? 'INFO').toUpperCase();
    return {
      totalLogs: logs.length,
      errorCount: logs.filter((item) => levelOf(item.level).includes('ERROR')).length,
      warnCount: logs.filter((item) => levelOf(item.level).includes('WARN')).length,
      infoCount: logs.filter((item) => levelOf(item.level).includes('INFO')).length,
      matchedTemplateCount: templates.length,
    };
  }

  private buildOverview(
    run: MonitorRunEntity,
    issues: LogAnalysisIssue[],
    stats: LogAnalysisReport['stats'],
  ) {
    if (issues.length) {
      return `${run.target.name} 最近共分析 ${stats.totalLogs} 条日志，识别出 ${issues.length} 类重点问题，当前最需要优先处理“${issues[0].title}”。`;
    }
    if (stats.warnCount > 0) {
      return `${run.target.name} 最近没有识别到明确故障模板，但存在 ${stats.warnCount} 条 WARN，建议继续观察。`;
    }
    return `${run.target.name} 最近日志整体平稳，暂未识别出需要升级处理的异常模式。`;
  }
}

function inferLevel(messageText: string) {
  if (/\bERROR\b|exception|failed|failure|denied/i.test(messageText)) return 'ERROR';
  if (/\bWARN\b|warning|timeout|timed out/i.test(messageText)) return 'WARN';
  return 'INFO';
}

function runSafeTime(logExcerpt: string) {
  const matched = logExcerpt.match(/\b(20\d{2}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})(?:,\d+)?\b/);
  if (matched) {
    const parsed = new Date(matched[1].replace(' ', 'T'));
    if (!Number.isNaN(parsed.getTime())) return parsed;
  }
  return new Date();
}
