import type { CurrentStreak, TrendPoint } from "../shared/api";

export interface IncidentAnalysis {
  incidentCount: number;
  longestOutage: null | number;
  mttr: null | number;
}

export interface MetricCheckpoint {
  durationMs: null | number;
  matched: boolean;
  timestamp: string;
}

export function analyzeIncidentSequence(checkpoints: MetricCheckpoint[], from: string, to: string): IncidentAnalysis {
  const sorted = sortCheckpoints(checkpoints);
  const fromTime = new Date(from).getTime();
  const toTime = new Date(to).getTime();
  const recoveredDurations: number[] = [];

  let incidentCount = 0;
  let longestOutage: null | number = null;
  let outageStart: null | number = null;
  let outageStartedAtWindowBoundary = false;
  let previousMatched: boolean | null = null;

  for (const checkpoint of sorted) {
    const timestamp = new Date(checkpoint.timestamp).getTime();

    if (!checkpoint.matched) {
      if (previousMatched !== false) {
        incidentCount++;
        outageStart = previousMatched === null ? fromTime : timestamp;
        outageStartedAtWindowBoundary = previousMatched === null;
      }
    } else if (previousMatched === false && outageStart !== null) {
      const duration = Math.max(0, timestamp - outageStart);
      longestOutage = maxNullable(longestOutage, duration);
      if (!outageStartedAtWindowBoundary) {
        recoveredDurations.push(duration);
      }
      outageStart = null;
      outageStartedAtWindowBoundary = false;
    }

    previousMatched = checkpoint.matched;
  }

  if (previousMatched === false && outageStart !== null) {
    const duration = Math.max(0, toTime - outageStart);
    longestOutage = maxNullable(longestOutage, duration);
  }

  return {
    incidentCount,
    longestOutage,
    mttr: calculateAverageDuration(recoveredDurations),
  };
}

export function buildHourlyTrend(checkpoints: MetricCheckpoint[]): TrendPoint[] {
  const buckets = new Map<
    string,
    {
      downChecks: number;
      durations: number[];
      totalChecks: number;
      upChecks: number;
    }
  >();

  for (const checkpoint of checkpoints) {
    const bucketStart = getUtcHourStart(checkpoint.timestamp);
    const bucket = buckets.get(bucketStart) ?? { downChecks: 0, durations: [], totalChecks: 0, upChecks: 0 };

    bucket.totalChecks++;
    if (checkpoint.matched) {
      bucket.upChecks++;
      if (checkpoint.durationMs !== null) {
        bucket.durations.push(checkpoint.durationMs);
      }
    } else {
      bucket.downChecks++;
    }

    buckets.set(bucketStart, bucket);
  }

  return [...buckets.entries()]
    .sort(([left], [right]) => left.localeCompare(right))
    .map(([bucketStart, bucket]) => ({
      availability: calculateAvailability(bucket.upChecks, bucket.totalChecks),
      avgDurationMs: calculateAverageDuration(bucket.durations),
      bucketStart,
      downChecks: bucket.downChecks,
      maxDurationMs: bucket.durations.length > 0 ? Math.max(...bucket.durations) : null,
      minDurationMs: bucket.durations.length > 0 ? Math.min(...bucket.durations) : null,
      totalChecks: bucket.totalChecks,
      upChecks: bucket.upChecks,
    }));
}

export function calculateAvailability(upChecks: number, totalChecks: number): number {
  if (totalChecks <= 0) return 0;
  return roundToTwo((upChecks / totalChecks) * 100);
}

export function calculateAverageDuration(durations: number[]): null | number {
  if (durations.length === 0) return null;
  const total = durations.reduce((sum, duration) => sum + duration, 0);
  return roundToTwo(total / durations.length);
}

export function calculateCurrentStreak(checkpoints: MetricCheckpoint[], limit?: number): CurrentStreak | null {
  const sorted = sortCheckpoints(checkpoints);
  const latest = sorted.at(-1);
  if (!latest) return null;

  let count = 0;
  for (let index = sorted.length - 1; index >= 0; index--) {
    const checkpoint = sorted[index];
    if (checkpoint?.matched !== latest.matched) break;
    count++;
  }

  return {
    ...(limit !== undefined && count >= limit ? { capped: true } : {}),
    count,
    up: latest.matched,
  };
}

export function calculatePercentile(durations: number[], percentile: number): null | number {
  if (durations.length === 0) return null;

  const sorted = [...durations].sort((a, b) => a - b);
  const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil((sorted.length * percentile) / 100) - 1));
  return sorted[index] ?? null;
}

function getUtcHourStart(timestamp: string): string {
  const date = new Date(timestamp);
  date.setUTCMinutes(0, 0, 0);
  return date.toISOString();
}

function maxNullable(left: null | number, right: number): number {
  return left === null ? right : Math.max(left, right);
}

function roundToTwo(value: number): number {
  return Math.round(value * 100) / 100;
}

function sortCheckpoints(checkpoints: MetricCheckpoint[]): MetricCheckpoint[] {
  return [...checkpoints].sort((left, right) => left.timestamp.localeCompare(right.timestamp));
}
