import type { AgentRiskInterpretError, AgentRiskInterpretRequest, AgentRiskInterpretResponse } from '@ftb/shared';
import { api } from './api';
import type { RiskReason, XiaobaoVersionRisk } from './xiaobao-risk';
import { findCachedInsight, type XiaobaoRiskInsightCacheItem } from './xiaobao-risk-cache';
import type { XiaobaoRiskSnapshot } from './xiaobao-risk-trend';

type RiskInsightCurrent = Pick<
  XiaobaoVersionRisk,
  'riskLevel' | 'riskScore' | 'confidence' | 'forecastReleaseDate' | 'signals' | 'trend'
>;

export type RiskInsightPrevious = Pick<
  XiaobaoRiskSnapshot,
  | 'riskScore'
  | 'confidence'
  | 'forecastReleaseDate'
  | 'openBugCount'
  | 'criticalBugCount'
  | 'failedTestCount'
  | 'blockedCount'
  | 'silentRiskCount'
>;

const SCORE_TRIGGER_DELTA = 15;
const CONFIDENCE_DROP_DELTA = 15;
const ONE_DAY_MS = 86_400_000;
const RISK_INSIGHT_COOLDOWN_MS = 6 * 60 * 60 * 1000;
const RISK_LEVEL_RANK: Record<XiaobaoVersionRisk['riskLevel'], number> = {
  on_track: 0,
  attention: 1,
  at_risk: 2,
  likely_delayed: 3,
  blocked: 4,
};

export function shouldRequestRiskInsight(current: RiskInsightCurrent, previous?: RiskInsightPrevious): boolean {
  if (current.riskLevel === 'on_track') return false;
  if (current.riskLevel === 'at_risk' || current.riskLevel === 'likely_delayed' || current.riskLevel === 'blocked') {
    return true;
  }

  const releaseIsNearWithUnfinishedWork =
    current.signals.daysToExpectedRelease !== undefined &&
    current.signals.daysToExpectedRelease <= 1 &&
    current.signals.unfinishedCount > 0;
  if (!previous) return isWorseningTrend(current) || releaseIsNearWithUnfinishedWork;

  const scoreDelta = current.riskScore - previous.riskScore;
  const confidenceDrop = previous.confidence - current.confidence;
  const forecastDelayMs = current.forecastReleaseDate && previous.forecastReleaseDate
    ? new Date(current.forecastReleaseDate).getTime() - new Date(previous.forecastReleaseDate).getTime()
    : 0;

  return (
    scoreDelta >= SCORE_TRIGGER_DELTA ||
    confidenceDrop >= CONFIDENCE_DROP_DELTA ||
    forecastDelayMs >= ONE_DAY_MS ||
    isWorseningTrend(current) ||
    current.signals.openBugCount > previous.openBugCount ||
    current.signals.criticalBugCount > (previous.criticalBugCount ?? 0) ||
    current.signals.failedTestCount > previous.failedTestCount ||
    current.signals.blockedCount > previous.blockedCount ||
    current.signals.silentRiskCount > previous.silentRiskCount ||
    releaseIsNearWithUnfinishedWork
  );
}

export function findLatestRiskInsightForVersion(
  cache: XiaobaoRiskInsightCacheItem[],
  versionId: string,
): XiaobaoRiskInsightCacheItem | undefined {
  return cache
    .filter((item) => item.versionId === versionId)
    .sort((a, b) => getTime(b.generatedAt) - getTime(a.generatedAt))[0];
}

export function shouldRequestRiskInsightWithCooldown(
  current: RiskInsightCurrent,
  previous?: RiskInsightPrevious,
  latestInsight?: XiaobaoRiskInsightCacheItem,
  now: Date = new Date(),
): boolean {
  if (!shouldRequestRiskInsight(current, previous)) return false;
  if (!latestInsight) return true;
  if (isRiskLevelEscalation(current.riskLevel, latestInsight)) return true;

  const generatedAt = getTime(latestInsight.generatedAt);
  const nowTime = now.getTime();
  if (!Number.isFinite(generatedAt) || !Number.isFinite(nowTime)) return true;
  return nowTime - generatedAt >= RISK_INSIGHT_COOLDOWN_MS;
}

export function shouldRequestRiskInsightWithCacheGate(
  riskCacheLoaded: boolean,
  cache: XiaobaoRiskInsightCacheItem[],
  current: XiaobaoVersionRisk,
  previous?: RiskInsightPrevious,
  now: Date = new Date(),
): boolean {
  if (!riskCacheLoaded) return false;
  if (getReusableInsight(cache, current)) return false;
  const latestInsight = findLatestRiskInsightForVersion(cache, current.versionId);
  return shouldRequestRiskInsightWithCooldown(current, previous, latestInsight, now);
}

export interface RiskInsightRequestGateInput {
  riskCacheLoaded: boolean;
  cache: XiaobaoRiskInsightCacheItem[];
  current: XiaobaoVersionRisk;
  previous?: RiskInsightPrevious;
  lastRequestedAt?: string;
  now?: Date;
}

export function shouldRequestRiskInsightWithRequestGate(input: RiskInsightRequestGateInput): boolean {
  const now = input.now ?? new Date();
  if (isRiskInsightRequestCoolingDown(input.lastRequestedAt, now)) return false;
  return shouldRequestRiskInsightWithCacheGate(
    input.riskCacheLoaded,
    input.cache,
    input.current,
    input.previous,
    now,
  );
}

export function isRiskInsightRequestCoolingDown(lastRequestedAt: string | undefined, now: Date = new Date()): boolean {
  if (!lastRequestedAt) return false;
  const requestedAt = getTime(lastRequestedAt);
  const nowTime = now.getTime();
  if (!Number.isFinite(requestedAt) || !Number.isFinite(nowTime)) return false;
  return nowTime - requestedAt < RISK_INSIGHT_COOLDOWN_MS;
}

export function buildRiskInsightSignature(risk: XiaobaoVersionRisk): string {
  return JSON.stringify({
    versionId: risk.versionId,
    riskScore: clampScore(risk.riskScore),
    riskLevel: risk.riskLevel,
    expectedReleaseDate: normalizeDateKey(risk.expectedReleaseDate),
    forecastReleaseDate: normalizeDateKey(risk.forecastReleaseDate),
    delayDays: risk.delayDays,
    confidence: clampScore(risk.confidence),
    signals: {
      unfinishedCount: risk.signals.unfinishedCount,
      openBugCount: risk.signals.openBugCount,
      criticalBugCount: risk.signals.criticalBugCount,
      failedTestCount: risk.signals.failedTestCount,
      blockedCount: risk.signals.blockedCount,
      silentRiskCount: risk.signals.silentRiskCount,
      daysToExpectedRelease: normalizeDaysToExpectedRelease(risk.signals.daysToExpectedRelease),
    },
    trend: {
      direction: risk.trend.direction,
      delta: risk.trend.delta,
      summary: risk.trend.summary,
      pattern: risk.trend.pattern ?? null,
    },
    reasons: normalizeReasons(risk.reasons),
    silentRisks: normalizeSilentRisks(risk.silentRisks),
    dailyEvidence: {
      todayDeliveries: normalizeEvidence(risk.dailyEvidence?.todayDeliveries),
      todayProgress: normalizeEvidence(risk.dailyEvidence?.todayProgress),
      todayCreations: normalizeEvidence(risk.dailyEvidence?.todayCreations),
      todayRisks: normalizeEvidence(risk.dailyEvidence?.todayRisks),
      progressNotes: normalizeEvidence(risk.dailyEvidence?.progressNotes),
      needsProgressItems: normalizeEvidence(risk.dailyEvidence?.needsProgressItems),
      recentActivityCount: risk.dailyEvidence?.recentActivityCount ?? 0,
      totalActivityCount: risk.dailyEvidence?.totalActivityCount ?? 0,
      todayActualHours: risk.dailyEvidence?.todayActualHours ?? 0,
      lastActivityAt: risk.dailyEvidence?.lastActivityAt ?? null,
    },
  });
}

export function buildRiskInsightDisplaySignature(risk: XiaobaoVersionRisk): string {
  return normalizeRiskInsightDisplaySignature(buildRiskInsightSignature(risk)) ?? buildRiskInsightSignature(risk);
}

export function normalizeRiskInsightDisplaySignature(signature: string): string | undefined {
  return buildDisplayCompatibilityKey(signature);
}

export function getReusableInsight(
  cache: XiaobaoRiskInsightCacheItem[],
  risk: XiaobaoVersionRisk,
): XiaobaoRiskInsightCacheItem | undefined {
  const currentSignature = buildRiskInsightSignature(risk);
  return (
    findCachedInsight(cache, risk.versionId, currentSignature) ??
    cache.find((item) => item.versionId === risk.versionId && normalizeCachedRiskSignature(item.riskSignature) === currentSignature) ??
    findLatestDisplayCompatibleInsight(cache, risk.versionId, currentSignature)
  );
}

export function findPreviousRiskSnapshot(
  snapshots: XiaobaoRiskSnapshot[],
  versionId: string,
  today: string,
): XiaobaoRiskSnapshot | undefined {
  return snapshots
    .filter((snapshot) => snapshot.versionId === versionId && snapshot.date < today)
    .sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0];
}

export function buildRiskInterpretRequest(risk: XiaobaoVersionRisk): AgentRiskInterpretRequest {
  return {
    versionId: risk.versionId,
    versionName: risk.versionName,
    productName: risk.productName,
    projectName: risk.projectName,
    riskScore: risk.riskScore,
    riskLevel: risk.riskLevel,
    expectedReleaseDate: risk.expectedReleaseDate,
    forecastReleaseDate: risk.forecastReleaseDate,
    delayDays: risk.delayDays,
    confidence: risk.confidence,
    signals: risk.signals,
    trendSummary: risk.trend.summary,
    reasons: risk.reasons.map(mapRiskReason),
    silentRisks: risk.silentRisks.map((item) => ({
      key: item.key,
      detail: item.detail,
    })),
    dailyEvidence: {
      todayDeliveries: summarizeEvidence(risk.dailyEvidence?.todayDeliveries),
      todayProgress: summarizeEvidence(risk.dailyEvidence?.todayProgress),
      todayCreations: summarizeEvidence(risk.dailyEvidence?.todayCreations),
      todayRisks: summarizeEvidence(risk.dailyEvidence?.todayRisks),
      progressNotes: summarizeEvidence(risk.dailyEvidence?.progressNotes),
      needsProgressItems: summarizeEvidence(risk.dailyEvidence?.needsProgressItems),
      recentActivityCount: risk.dailyEvidence?.recentActivityCount ?? 0,
      totalActivityCount: risk.dailyEvidence?.totalActivityCount ?? 0,
      todayActualHours: risk.dailyEvidence?.todayActualHours ?? 0,
      lastActivityAt: risk.dailyEvidence?.lastActivityAt,
    },
  };
}

export async function requestRiskInsight(
  risk: XiaobaoVersionRisk,
): Promise<AgentRiskInterpretResponse | AgentRiskInterpretError> {
  return api.postRaw<AgentRiskInterpretResponse | AgentRiskInterpretError>(
    '/ai/risk-interpret',
    buildRiskInterpretRequest(risk),
    120000,
  );
}

function mapRiskReason(reason: RiskReason): AgentRiskInterpretRequest['reasons'][number] {
  return {
    key: reason.key,
    label: reason.title,
    severity: mapReasonSeverity(reason),
    detail: reason.detail,
  };
}

function mapReasonSeverity(reason: RiskReason): AgentRiskInterpretRequest['reasons'][number]['severity'] {
  if (reason.severity === 'info') return 'low';
  if (reason.severity === 'warning') return 'medium';
  if (reason.key === 'critical_bug' || reason.key === 'blocked_work') return 'critical';
  return 'high';
}

function summarizeEvidence(items: Array<{ summary: string }> | undefined): string[] {
  return (items ?? []).map((item) => item.summary).filter((summary) => summary.trim().length > 0);
}

function isWorseningTrend(current: RiskInsightCurrent): boolean {
  return current.trend.direction === 'up' && current.trend.pattern === 'continuous_rising';
}

function normalizeReasons(reasons: XiaobaoVersionRisk['reasons']) {
  return reasons
    .map((reason) => ({
      key: reason.key,
      title: reason.title,
      severity: reason.severity,
      detail: reason.detail,
      count: reason.count ?? null,
    }))
    .sort(compareSignatureRows);
}

function normalizeSilentRisks(silentRisks: XiaobaoVersionRisk['silentRisks']) {
  return silentRisks
    .map((item) => ({
      key: item.key,
      title: item.title,
      detail: item.detail,
      itemId: item.itemId ?? null,
      itemType: item.itemType ?? null,
    }))
    .sort(compareSignatureRows);
}

function normalizeEvidence(items: Array<{ id?: string; title?: string; summary: string; occurredAt?: string }> | undefined) {
  return (items ?? [])
    .map((item) => ({
      id: item.id ?? null,
      title: item.title ?? null,
      summary: item.summary,
      occurredAt: item.occurredAt ?? null,
    }))
    .sort(compareSignatureRows);
}

function compareSignatureRows<T>(a: T, b: T): number {
  return JSON.stringify(a).localeCompare(JSON.stringify(b));
}

function clampScore(score: number): number {
  return Math.max(0, Math.min(100, Math.round(score)));
}

function isRiskLevelEscalation(
  currentLevel: XiaobaoVersionRisk['riskLevel'],
  latestInsight: XiaobaoRiskInsightCacheItem,
): boolean {
  const previousLevel = parseRiskLevel(latestInsight.riskSignature);
  if (!previousLevel) return false;
  return RISK_LEVEL_RANK[currentLevel] > RISK_LEVEL_RANK[previousLevel];
}

function parseRiskLevel(signature: string): XiaobaoVersionRisk['riskLevel'] | undefined {
  try {
    const parsed = JSON.parse(signature) as { riskLevel?: XiaobaoVersionRisk['riskLevel'] };
    return parsed.riskLevel && parsed.riskLevel in RISK_LEVEL_RANK ? parsed.riskLevel : undefined;
  } catch {
    return undefined;
  }
}

function normalizeCachedRiskSignature(signature: string): string | undefined {
  try {
    const parsed = JSON.parse(signature) as {
      expectedReleaseDate?: string | null;
      forecastReleaseDate?: string | null;
      signals?: { daysToExpectedRelease?: number | null };
    };
    return JSON.stringify({
      ...parsed,
      expectedReleaseDate: normalizeDateKey(parsed.expectedReleaseDate),
      forecastReleaseDate: normalizeDateKey(parsed.forecastReleaseDate),
      signals: {
        ...parsed.signals,
        daysToExpectedRelease: normalizeDaysToExpectedRelease(parsed.signals?.daysToExpectedRelease),
      },
    });
  } catch {
    return undefined;
  }
}

function findLatestDisplayCompatibleInsight(
  cache: XiaobaoRiskInsightCacheItem[],
  versionId: string,
  currentSignature: string,
): XiaobaoRiskInsightCacheItem | undefined {
  const currentKey = buildDisplayCompatibilityKey(currentSignature);
  if (!currentKey) return undefined;
  return cache
    .filter((item) => item.versionId === versionId && buildDisplayCompatibilityKey(item.riskSignature) === currentKey)
    .sort((a, b) => getTime(b.generatedAt) - getTime(a.generatedAt))[0];
}

function buildDisplayCompatibilityKey(signature: string): string | undefined {
  try {
    const parsed = JSON.parse(signature) as Record<string, unknown>;
    const signals = readRecord(parsed.signals);
    const trend = readRecord(parsed.trend);
    return JSON.stringify({
      versionId: readString(parsed.versionId),
      riskScore: normalizeInteger(parsed.riskScore),
      riskLevel: readString(parsed.riskLevel),
      expectedReleaseDate: normalizeDateKey(readString(parsed.expectedReleaseDate)),
      delayDays: normalizeInteger(parsed.delayDays),
      confidence: normalizeInteger(parsed.confidence),
      signals: {
        unfinishedCount: normalizeInteger(signals.unfinishedCount),
        openBugCount: normalizeInteger(signals.openBugCount),
        criticalBugCount: normalizeInteger(signals.criticalBugCount),
        failedTestCount: normalizeInteger(signals.failedTestCount),
        blockedCount: normalizeInteger(signals.blockedCount),
        silentRiskCount: normalizeInteger(signals.silentRiskCount),
      },
      trend: {
        direction: readString(trend.direction),
        delta: normalizeInteger(trend.delta),
        pattern: readString(trend.pattern),
      },
      reasons: normalizeCompatibilityReasons(parsed.reasons),
      silentRisks: normalizeCompatibilitySilentRisks(parsed.silentRisks),
    });
  } catch {
    return undefined;
  }
}

function normalizeCompatibilityReasons(rows: unknown) {
  return asRecordArray(rows)
    .map((row) => ({
      key: readString(row.key),
      severity: readString(row.severity),
      detail: readString(row.detail),
      count: normalizeInteger(row.count),
    }))
    .sort(compareSignatureRows);
}

function normalizeCompatibilitySilentRisks(rows: unknown) {
  return asRecordArray(rows)
    .map((row) => ({
      key: readString(row.key),
      itemId: readString(row.itemId),
      itemType: readString(row.itemType),
    }))
    .sort(compareSignatureRows);
}

function asRecordArray(rows: unknown): Array<Record<string, unknown>> {
  return Array.isArray(rows) ? rows.map(readRecord).filter((row) => Object.keys(row).length > 0) : [];
}

function readRecord(value: unknown): Record<string, unknown> {
  return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
}

function readString(value: unknown): string | null {
  return typeof value === 'string' ? value : null;
}

function normalizeDateKey(value: string | null | undefined): string | null {
  if (!value) return null;
  const raw = value.trim();
  if (raw.length === 0) return null;
  if (/^\d{4}-\d{2}-\d{2}/.test(raw)) return raw.slice(0, 10);

  const time = new Date(raw).getTime();
  if (!Number.isFinite(time)) return raw;
  return new Date(time).toISOString().slice(0, 10);
}

function normalizeDaysToExpectedRelease(value: number | null | undefined): number | null {
  if (value === null || value === undefined || !Number.isFinite(value)) return null;
  return value >= 0 ? Math.ceil(value) : Math.floor(value);
}

function normalizeInteger(value: unknown): number | null {
  return typeof value === 'number' && Number.isFinite(value) ? Math.round(value) : null;
}

function getTime(value: string): number {
  const time = new Date(value).getTime();
  return Number.isFinite(time) ? time : Number.NaN;
}
