import type { TelemetryEvent } from './telemetryEvents.js';
import { kstTodayIso } from '../../shared/util/kstDate.js';

function kstDate(ts: string): string {
  return kstTodayIso(new Date(ts));
}

interface DailyRow {
  date: string;
  capture: number;
  ai_succeeded: number;
  ai_failed: number;
  trash: number;
  restore: number;
  permanent_delete: number;
  empty_trash: number;
  expired_banner_shown: number;
  expired_batch_trash: number;
  ollama_unreachable: number;
  ollama_recovered: number;
  ollama_recheck_manual: number;
  ai_retry_manual: number;
  tag_vocab_hit: number;
  tag_vocab_miss: number;
  recall_shown: number;
  recall_opened: number;
  recall_dismissed: number;
  recall_snoozed: number;
}

export interface StatsResult {
  md: string;
  eventCount: number;
}

export function aggregateStats(events: TelemetryEvent[], generatedAt: Date): StatsResult {
  const eventCount = events.length;
  const byDay = new Map<string, DailyRow>();
  let aiSucceeded = 0;
  let aiFailed = 0;
  let durationSum = 0;
  let durationN = 0;
  let trashCount = 0;
  let restoreCount = 0;
  let expiredBannerShownCandidatesSum = 0;
  let expiredBatchTrashCountSum = 0;
  let ollamaDowntimeSum = 0;
  let ollamaRecoveredCount = 0;
  let ollamaRecheckManualCount = 0;
  let aiRetryManualCount = 0;
  let aiRetryManualFailedSum = 0;
  let tagVocabHitCount = 0;
  let tagVocabMissCount = 0;
  let recallShownCount = 0;
  let recallOpenedCount = 0;
  let recallDismissedCount = 0;
  let recallSnoozedCount = 0;
  let recallAgeDaysSum = 0;
  for (const ev of events) {
    const day = kstDate(ev.ts);
    let row = byDay.get(day);
    if (!row) {
      row = {
        date: day,
        capture: 0, ai_succeeded: 0, ai_failed: 0,
        trash: 0, restore: 0, permanent_delete: 0, empty_trash: 0,
        expired_banner_shown: 0, expired_batch_trash: 0,
        ollama_unreachable: 0, ollama_recovered: 0, ollama_recheck_manual: 0,
        ai_retry_manual: 0,
        tag_vocab_hit: 0, tag_vocab_miss: 0,
        recall_shown: 0, recall_opened: 0, recall_dismissed: 0, recall_snoozed: 0
      };
      byDay.set(day, row);
    }
    if (ev.kind === 'capture') row.capture += 1;
    else if (ev.kind === 'ai_succeeded') {
      row.ai_succeeded += 1;
      aiSucceeded += 1;
      durationSum += ev.payload.durationMs;
      durationN += 1;
    } else if (ev.kind === 'ai_failed') {
      row.ai_failed += 1;
      aiFailed += 1;
    } else if (ev.kind === 'trash') {
      row.trash += 1;
      trashCount += 1;
    } else if (ev.kind === 'restore') {
      row.restore += 1;
      restoreCount += 1;
    } else if (ev.kind === 'permanent_delete') {
      row.permanent_delete += 1;
    } else if (ev.kind === 'empty_trash') {
      row.empty_trash += 1;
    } else if (ev.kind === 'expired_banner_shown') {
      row.expired_banner_shown += 1;
      expiredBannerShownCandidatesSum += ev.payload.candidateCount;
    } else if (ev.kind === 'expired_batch_trash') {
      row.expired_batch_trash += 1;
      expiredBatchTrashCountSum += ev.payload.count;
    } else if (ev.kind === 'ollama_unreachable') {
      row.ollama_unreachable += 1;
    } else if (ev.kind === 'ollama_recovered') {
      row.ollama_recovered += 1;
      ollamaDowntimeSum += ev.payload.downtimeMs;
      ollamaRecoveredCount += 1;
    } else if (ev.kind === 'ollama_recheck_manual') {
      row.ollama_recheck_manual += 1;
      ollamaRecheckManualCount += 1;
    } else if (ev.kind === 'ai_retry_manual') {
      row.ai_retry_manual += 1;
      aiRetryManualCount += 1;
      aiRetryManualFailedSum += ev.payload.failedCount;
    } else if (ev.kind === 'tag_vocab_hit') {
      row.tag_vocab_hit += 1;
      tagVocabHitCount += 1;
    } else if (ev.kind === 'tag_vocab_miss') {
      row.tag_vocab_miss += 1;
      tagVocabMissCount += 1;
    } else if (ev.kind === 'recall_shown') {
      row.recall_shown += 1;
      recallShownCount += 1;
      recallAgeDaysSum += ev.payload.ageDays;
    } else if (ev.kind === 'recall_opened') {
      row.recall_opened += 1;
      recallOpenedCount += 1;
    } else if (ev.kind === 'recall_dismissed') {
      row.recall_dismissed += 1;
      recallDismissedCount += 1;
    } else if (ev.kind === 'recall_snoozed') {
      row.recall_snoozed += 1;
      recallSnoozedCount += 1;
    } else {
      // v0.2.6 #8 — 새 telemetry kind 추가 시 본 함수 분기 누락을 컴파일 단계에서 catch.
      const _exhaustive: never = ev;
      void _exhaustive;
    }
  }
  const days = Array.from(byDay.values()).sort((a, b) => a.date.localeCompare(b.date));
  const aiTotal = aiSucceeded + aiFailed;
  const successRate = aiTotal === 0 ? 'N/A' : `${(aiSucceeded / aiTotal * 100).toFixed(1)}% (${aiSucceeded}/${aiTotal})`;
  const avgDuration = durationN === 0 ? 'N/A' : `${Math.round(durationSum / durationN)}`;
  // v0.2.6 #9 — 회수율 = restore / trash event 비율 (event-level — 한 노트 trash-restore 반복 시
  // 100% 가능, unique-note 회수율 아님. spec §6.2 "회수 도구 동작?" 질문에 충분).
  const trashRecoveryRate = trashCount === 0
    ? 'N/A'
    : `${(restoreCount / trashCount * 100).toFixed(1)}% (${restoreCount}/${trashCount})`;
  const expiredTrashRatio = expiredBannerShownCandidatesSum === 0
    ? 'N/A'
    : `${(expiredBatchTrashCountSum / expiredBannerShownCandidatesSum * 100).toFixed(1)}% (${expiredBatchTrashCountSum}/${expiredBannerShownCandidatesSum})`;
  const avgDowntime = ollamaRecoveredCount === 0
    ? 'N/A'
    : `${Math.round(ollamaDowntimeSum / ollamaRecoveredCount)}`;
  const totalUnreachable = days.reduce((s, r) => s + r.ollama_unreachable, 0);
  const tagVocabTotal = tagVocabHitCount + tagVocabMissCount;
  const tagVocabSummary = tagVocabTotal === 0
    ? '(데이터 없음)'
    : `hit/miss = ${tagVocabHitCount}/${tagVocabMissCount} (적중률 ${(tagVocabHitCount / tagVocabTotal * 100).toFixed(1)}%)`;
  const recallSummary = recallShownCount === 0
    ? '(데이터 없음)'
    : `shown ${recallShownCount} / opened ${recallOpenedCount} / dismissed ${recallDismissedCount} / snoozed ${recallSnoozedCount} (열림율 ${(recallOpenedCount / recallShownCount * 100).toFixed(1)}%)`;
  const recallAvgAge = recallShownCount === 0
    ? '(데이터 없음)'
    : `${Math.round(recallAgeDaysSum / recallShownCount)}`;
  const lines: string[] = [];
  lines.push('# Inkling Telemetry Stats');
  lines.push('');
  lines.push(`생성: ${generatedAt.toISOString()}`);
  lines.push(`총 이벤트: ${eventCount}`);
  lines.push('');
  lines.push('## 일자별 카운트');
  lines.push('');
  lines.push('| 일자 | capture | ai_succeeded | ai_failed | trash | restore | permanent_delete | empty_trash | expired_banner_shown | expired_batch_trash | ollama_unreachable | ollama_recovered | ollama_recheck_manual | ai_retry_manual | tag_vocab_hit | tag_vocab_miss | recall_shown | recall_opened | recall_dismissed | recall_snoozed |');
  lines.push('|------|---------|--------------|-----------|-------|---------|------------------|-------------|----------------------|---------------------|--------------------|------------------|----------------------|-----------------|---------------|----------------|--------------|---------------|------------------|----------------|');
  for (const row of days) {
    lines.push(`| ${row.date} | ${row.capture} | ${row.ai_succeeded} | ${row.ai_failed} | ${row.trash} | ${row.restore} | ${row.permanent_delete} | ${row.empty_trash} | ${row.expired_banner_shown} | ${row.expired_batch_trash} | ${row.ollama_unreachable} | ${row.ollama_recovered} | ${row.ollama_recheck_manual} | ${row.ai_retry_manual} | ${row.tag_vocab_hit} | ${row.tag_vocab_miss} | ${row.recall_shown} | ${row.recall_opened} | ${row.recall_dismissed} | ${row.recall_snoozed} |`);
  }
  lines.push('');
  lines.push('## 핵심 ratio');
  lines.push('');
  lines.push(`- AI 성공률: ${successRate}`);
  lines.push(`- 평균 ai_succeeded durationMs: ${avgDuration}`);
  lines.push(`- 휴지통 회수율: ${trashRecoveryRate}`);
  lines.push(`- 만료 trash ratio: ${expiredTrashRatio}`);
  lines.push(`- Ollama unreachable 빈도: ${totalUnreachable}건`);
  lines.push(`- 평균 downtimeMs (recovered): ${avgDowntime}`);
  lines.push(`- 수동 recheck 사용량: ${ollamaRecheckManualCount}건`);
  lines.push(`- AI 수동 재시도: ${aiRetryManualCount}회 / 누적 ${aiRetryManualFailedSum}건`);
  lines.push(`- 태그 vocab: ${tagVocabSummary}`);
  lines.push(`- 회상 추천: ${recallSummary}`);
  lines.push(`- 회상 평균 ageDays: ${recallAvgAge}`);
  lines.push('');
  return { md: lines.join('\n'), eventCount };
}
