import type { VersionWithContext } from './derive';
import type { V22XiaobaoWarningSummary } from './v22-api';
import type {
  RiskReason,
  SilentRisk,
  XiaobaoConfidenceLevel,
  XiaobaoRiskLevel,
  XiaobaoRiskSignals,
  XiaobaoVersionRisk,
} from './xiaobao-risk';
import type { EvidenceItem, VersionDailyEvidence } from './xiaobao-risk-evidence';
import { summarizeRiskTrendWithCurrent, type XiaobaoRiskSnapshot } from './xiaobao-risk-trend';

interface BuildXiaobaoRisksFromV22SummariesInput {
  summaries: V22XiaobaoWarningSummary[];
  versions: VersionWithContext[];
  snapshots?: XiaobaoRiskSnapshot[];
  now?: Date;
}

export type V22XiaobaoSummaryLoadState = 'idle' | 'loading' | 'ready' | 'empty' | 'failed';

const RISK_LEVELS = new Set<XiaobaoRiskLevel>(['on_track', 'attention', 'at_risk', 'likely_delayed', 'blocked']);
const REASON_SEVERITIES = new Set<RiskReason['severity']>(['info', 'warning', 'danger']);
const SILENT_RISK_ITEM_TYPES = new Set<NonNullable<SilentRisk['itemType']>>(['dev_task', 'test_case', 'bug', 'version']);

export function shouldLoadXiaobaoAppDataFallback(state: V22XiaobaoSummaryLoadState): boolean {
  return state === 'empty' || state === 'failed';
}

export function filterV22XiaobaoSummariesForVisibleVersions(
  summaries: V22XiaobaoWarningSummary[],
  versions: VersionWithContext[],
): V22XiaobaoWarningSummary[] {
  const visibleVersionIds = new Set(versions.map((version) => version.id));
  if (visibleVersionIds.size === 0) return [];
  return summaries.filter((summary) => visibleVersionIds.has(summary.versionId));
}

export function buildXiaobaoRisksFromV22Summaries(
  input: BuildXiaobaoRisksFromV22SummariesInput,
): XiaobaoVersionRisk[] {
  const now = input.now ?? new Date();
  const versionMap = new Map(input.versions.map((version) => [version.id, version]));

  return input.summaries
    .map((summary) => mapSummaryToRisk(summary, versionMap.get(summary.versionId), input.snapshots ?? [], now))
    .sort((a, b) => b.riskScore - a.riskScore);
}

function mapSummaryToRisk(
  row: V22XiaobaoWarningSummary,
  version: VersionWithContext | undefined,
  snapshots: XiaobaoRiskSnapshot[],
  now: Date,
): XiaobaoVersionRisk {
  const payload = getRiskPayload(row.summary);
  const riskLevel = toRiskLevel(row.riskLevel) ?? toRiskLevel(readString(payload.riskLevel)) ?? 'attention';
  const riskScore = clampScore(readNumber(payload.riskScore) ?? row.riskScore);
  const confidence = clampScore(readNumber(payload.confidence) ?? row.confidence);
  const forecastReleaseDate = readString(payload.forecastReleaseDate) ?? row.forecastReleaseDate;
  const expectedReleaseDate =
    readString(payload.expectedReleaseDate)
    ?? version?.expectedReleaseDate
    ?? version?.releaseDate
    ?? null;
  const delayDays = nonNegativeNumber(readNumber(payload.delayDays)) ?? calcDelayDays(expectedReleaseDate, forecastReleaseDate);
  const remainingWorkHours = nonNegativeNumber(readNumber(payload.remainingWorkHours)) ?? 0;
  const dailyEvidence = readDailyEvidence(payload.dailyEvidence);
  const silentRisks = readSilentRisks(payload.silentRisks);
  const signals = readSignals(payload.signals, dailyEvidence, silentRisks, expectedReleaseDate, now);
  const reasons = readReasons(payload.reasons);
  const sourceTime = row.recomputedAt ?? row.updatedAt ?? now.toISOString();
  const currentSnapshot = buildCurrentSnapshot({
    payload,
    row,
    riskLevel,
    riskScore,
    confidence,
    forecastReleaseDate,
    signals,
    sourceTime,
    now,
  });
  const trend = readTrend(payload.trend)
    ?? summarizeRiskTrendWithCurrent(
      snapshots.filter((snapshot) => snapshot.versionId === row.versionId),
      currentSnapshot,
    );

  return {
    versionId: row.versionId,
    versionName: readString(payload.versionName) ?? version?.name ?? row.versionId,
    productId: readString(payload.productId) ?? version?.productId,
    productName: readString(payload.productName) ?? version?.productName,
    projectId: readString(payload.projectId) ?? version?.projectId,
    projectName: readString(payload.projectName) ?? version?.projectName,
    riskScore,
    riskLevel,
    expectedReleaseDate,
    confidence,
    confidenceLevel: getConfidenceLevel(confidence),
    forecastReleaseDate,
    delayDays,
    remainingWorkHours,
    reasons: reasons.length > 0 ? reasons : buildDefaultReasons(riskLevel, riskScore),
    silentRisks,
    dailyEvidence,
    signals,
    currentSnapshot,
    trend,
  };
}

function getRiskPayload(summary: unknown): Record<string, unknown> {
  const root = readRecord(summary);
  const nested = readRecord(root.risk);
  return Object.keys(nested).length > 0 ? nested : root;
}

function buildCurrentSnapshot(input: {
  payload: Record<string, unknown>;
  row: V22XiaobaoWarningSummary;
  riskLevel: XiaobaoRiskLevel;
  riskScore: number;
  confidence: number;
  forecastReleaseDate?: string;
  signals: XiaobaoRiskSignals;
  sourceTime: string;
  now: Date;
}): XiaobaoRiskSnapshot {
  const snapshot = readRecord(input.payload.currentSnapshot);
  const createdAt = readString(snapshot.createdAt) ?? input.sourceTime ?? input.now.toISOString();
  return {
    versionId: input.row.versionId,
    date: readString(snapshot.date) ?? createdAt.slice(0, 10),
    riskScore: clampScore(readNumber(snapshot.riskScore) ?? input.riskScore),
    riskLevel: toRiskLevel(readString(snapshot.riskLevel)) ?? input.riskLevel,
    forecastReleaseDate: readString(snapshot.forecastReleaseDate) ?? input.forecastReleaseDate,
    openBugCount: nonNegativeInteger(readNumber(snapshot.openBugCount)) ?? input.signals.openBugCount,
    criticalBugCount: nonNegativeInteger(readNumber(snapshot.criticalBugCount)) ?? input.signals.criticalBugCount,
    failedTestCount: nonNegativeInteger(readNumber(snapshot.failedTestCount)) ?? input.signals.failedTestCount,
    blockedCount: nonNegativeInteger(readNumber(snapshot.blockedCount)) ?? input.signals.blockedCount,
    silentRiskCount: nonNegativeInteger(readNumber(snapshot.silentRiskCount)) ?? input.signals.silentRiskCount,
    confidence: clampScore(readNumber(snapshot.confidence) ?? input.confidence),
    createdAt,
  };
}

function readSignals(
  value: unknown,
  dailyEvidence: VersionDailyEvidence | undefined,
  silentRisks: SilentRisk[],
  expectedReleaseDate: string | null,
  now: Date,
): XiaobaoRiskSignals {
  const row = readRecord(value);
  return {
    unfinishedCount: nonNegativeInteger(readNumber(row.unfinishedCount)) ?? 0,
    openBugCount: nonNegativeInteger(readNumber(row.openBugCount)) ?? 0,
    criticalBugCount: nonNegativeInteger(readNumber(row.criticalBugCount)) ?? 0,
    failedTestCount: nonNegativeInteger(readNumber(row.failedTestCount)) ?? 0,
    blockedCount: nonNegativeInteger(readNumber(row.blockedCount)) ?? 0,
    silentRiskCount:
      nonNegativeInteger(readNumber(row.silentRiskCount))
      ?? dailyEvidence?.silentRisks?.length
      ?? silentRisks.length,
    daysToExpectedRelease: readNumber(row.daysToExpectedRelease) ?? calcDaysToExpectedRelease(expectedReleaseDate, now),
  };
}

function readReasons(value: unknown): RiskReason[] {
  if (!Array.isArray(value)) return [];
  return value.map((item) => {
    const row = readRecord(item);
    const key = readString(row.key);
    const title = readString(row.title);
    const detail = readString(row.detail);
    if (!key || !title || !detail) return undefined;
    return {
      key,
      title,
      detail,
      severity: toReasonSeverity(readString(row.severity)),
      count: nonNegativeInteger(readNumber(row.count)),
    };
  }).filter(isDefined);
}

function readSilentRisks(value: unknown): SilentRisk[] {
  if (!Array.isArray(value)) return [];
  return value.map((item) => {
    const row = readRecord(item);
    const key = readString(row.key);
    const title = readString(row.title);
    const detail = readString(row.detail);
    if (!key || !title || !detail) return undefined;
    return {
      key,
      title,
      detail,
      itemId: readString(row.itemId),
      itemType: toSilentRiskItemType(readString(row.itemType)),
    };
  }).filter(isDefined);
}

function readDailyEvidence(value: unknown): VersionDailyEvidence | undefined {
  const row = readRecord(value);
  if (Object.keys(row).length === 0) return undefined;
  return {
    todayDeliveries: readEvidenceItems(row.todayDeliveries),
    todayProgress: readEvidenceItems(row.todayProgress),
    todayCreations: readEvidenceItems(row.todayCreations),
    todayRisks: readEvidenceItems(row.todayRisks),
    progressNotes: readEvidenceItems(row.progressNotes),
    needsProgressItems: readEvidenceItems(row.needsProgressItems),
    recentActivityCount: nonNegativeInteger(readNumber(row.recentActivityCount)) ?? 0,
    totalActivityCount: nonNegativeInteger(readNumber(row.totalActivityCount)) ?? 0,
    todayActualHours: nonNegativeNumber(readNumber(row.todayActualHours)) ?? 0,
    lastActivityAt: readString(row.lastActivityAt),
    silentRisks: readSilentRisks(row.silentRisks),
  };
}

function readEvidenceItems(value: unknown): EvidenceItem[] {
  if (!Array.isArray(value)) return [];
  return value.map((item, index) => {
    const row = readRecord(item);
    const summary = readString(row.summary);
    if (!summary) return undefined;
    return {
      id: readString(row.id) ?? `v22-evidence-${index}`,
      title: readString(row.title) ?? summary,
      summary,
      occurredAt: readString(row.occurredAt) ?? '',
      actorId: readString(row.actorId),
    };
  }).filter(isDefined);
}

function readTrend(value: unknown): XiaobaoVersionRisk['trend'] | undefined {
  const row = readRecord(value);
  const direction = readString(row.direction);
  const summary = readString(row.summary);
  if (!direction || !summary) return undefined;
  if (direction !== 'up' && direction !== 'down' && direction !== 'flat' && direction !== 'unknown') return undefined;
  const pattern = readString(row.pattern);
  return {
    direction,
    delta: readNumber(row.delta) ?? 0,
    summary,
    pattern: pattern === 'continuous_rising'
      || pattern === 'continuous_falling'
      || pattern === 'score_delta'
      || pattern === 'stable'
      || pattern === 'unknown'
      ? pattern
      : undefined,
  };
}

function buildDefaultReasons(riskLevel: XiaobaoRiskLevel, riskScore: number): RiskReason[] {
  if (riskLevel === 'on_track') return [];
  return [{
    key: 'precomputed_risk',
    title: 'Precomputed risk',
    detail: `V2.2 precomputed summary reports risk score ${riskScore}.`,
    severity: riskScore >= 75 ? 'danger' : 'warning',
  }];
}

function toRiskLevel(value: string | undefined): XiaobaoRiskLevel | undefined {
  return value && RISK_LEVELS.has(value as XiaobaoRiskLevel) ? value as XiaobaoRiskLevel : undefined;
}

function toReasonSeverity(value: string | undefined): RiskReason['severity'] {
  return value && REASON_SEVERITIES.has(value as RiskReason['severity'])
    ? value as RiskReason['severity']
    : 'warning';
}

function toSilentRiskItemType(value: string | undefined): SilentRisk['itemType'] | undefined {
  return value && SILENT_RISK_ITEM_TYPES.has(value as NonNullable<SilentRisk['itemType']>)
    ? value as NonNullable<SilentRisk['itemType']>
    : undefined;
}

function getConfidenceLevel(confidence: number): XiaobaoConfidenceLevel {
  if (confidence >= 75) return 'high';
  if (confidence >= 50) return 'medium';
  return 'low';
}

function calcDelayDays(expectedReleaseDate: string | null, forecastReleaseDate: string | undefined): number {
  const expected = parseDate(expectedReleaseDate);
  const forecast = parseDate(forecastReleaseDate);
  if (!expected || !forecast || forecast.getTime() <= expected.getTime()) return 0;
  return Math.round(((forecast.getTime() - expected.getTime()) / 86_400_000) * 10) / 10;
}

function calcDaysToExpectedRelease(expectedReleaseDate: string | null, now: Date): number | undefined {
  const expected = parseDate(expectedReleaseDate);
  if (!expected) return undefined;
  return Math.round(((expected.getTime() - now.getTime()) / 86_400_000) * 10) / 10;
}

function parseDate(value: string | null | undefined): Date | undefined {
  if (!value) return undefined;
  const date = new Date(value);
  return Number.isFinite(date.getTime()) ? date : undefined;
}

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 | undefined {
  return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
}

function readNumber(value: unknown): number | undefined {
  if (typeof value === 'number' && Number.isFinite(value)) return value;
  if (typeof value === 'string' && value.trim().length > 0) {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : undefined;
  }
  return undefined;
}

function nonNegativeNumber(value: number | undefined): number | undefined {
  return value !== undefined && value >= 0 ? value : undefined;
}

function nonNegativeInteger(value: number | undefined): number | undefined {
  return value !== undefined && value >= 0 ? Math.round(value) : undefined;
}

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

function isDefined<T>(value: T | undefined): value is T {
  return value !== undefined;
}
