import { Inject, Injectable, NotFoundException } from '@nestjs/common';
import { JobsService } from '../jobs/jobs.service';
import type { XiaobaoRiskLevel, XiaobaoRiskSummaryPayload } from '../xiaobao/xiaobao-risk.types';
import {
  XIAOBAO_AI_COOLDOWN_MS,
  XIAOBAO_AI_INTERPRETER,
  XIAOBAO_AI_INTERPRET_JOB,
  XIAOBAO_AI_PRISMA,
  XIAOBAO_RISK_LEVEL_RANK,
  XiaobaoAiEvaluateOptions,
  XiaobaoAiEvaluateResult,
  XiaobaoAiGenerationResult,
  XiaobaoAiInterpreter,
  XiaobaoAiInterpretPayload,
  XiaobaoAiRiskInterpretRequest,
} from './xiaobao-ai.types';

@Injectable()
export class XiaobaoAiService {
  constructor(
    @Inject(XIAOBAO_AI_PRISMA) private readonly prisma: any,
    private readonly jobs: JobsService,
    @Inject(XIAOBAO_AI_INTERPRETER) private readonly ai: XiaobaoAiInterpreter,
  ) {}

  async evaluateSummary(
    summary: XiaobaoRiskSummaryPayload,
    options: XiaobaoAiEvaluateOptions = {},
  ): Promise<XiaobaoAiEvaluateResult> {
    const now = options.now ?? new Date();
    const gate = await this.evaluateGenerationGate(summary, now);
    if (!gate.allow) return { enqueued: false, reason: gate.reason };

    const job = await this.jobs.enqueue({
      type: XIAOBAO_AI_INTERPRET_JOB,
      dedupeKey: buildDedupeKey(summary.versionId, summary.riskSignature),
      payload: {
        versionId: summary.versionId,
        riskSignature: summary.riskSignature,
      },
      maxAttempts: 3,
    });

    return {
      enqueued: true,
      reason: gate.escalated ? 'risk_escalated' : 'queued',
      jobId: job?.id,
    };
  }

  async evaluateCurrentSummary(
    versionId: string,
    options: XiaobaoAiEvaluateOptions = {},
  ): Promise<XiaobaoAiEvaluateResult> {
    const row = await this.prisma.xiaobaoRiskSummary.findUnique({ where: { versionId } });
    if (!row) throw new NotFoundException('Xiaobao risk summary not found');
    return this.evaluateSummary(coerceSummary(row), options);
  }

  async runInterpretation(
    payload: XiaobaoAiInterpretPayload,
    options: XiaobaoAiEvaluateOptions = {},
  ): Promise<XiaobaoAiGenerationResult> {
    const now = options.now ?? new Date();
    const row = await this.prisma.xiaobaoRiskSummary.findUnique({ where: { versionId: payload.versionId } });
    if (!row) throw new NotFoundException('Xiaobao risk summary not found');

    const summary = coerceSummary(row);
    if (summary.riskSignature !== payload.riskSignature) {
      return { generated: false, reason: 'stale' };
    }

    const gate = await this.evaluateGenerationGate(summary, now);
    if (!gate.allow) return { generated: false, reason: gate.reason };

    const response = await this.ai.interpretRisk(buildRiskInterpretRequest(summary));
    if (!response.ok) {
      throw new Error(response.error || 'AI risk interpretation failed');
    }

    await this.prisma.xiaobaoRiskInsight.create({
      data: {
        versionId: summary.versionId,
        riskSignature: summary.riskSignature,
        status: 'generated',
        insight: {
          versionId: summary.versionId,
          riskSignature: summary.riskSignature,
          generatedAt: now.toISOString(),
          insight: response.result,
          providerInfo: { model: response.meta.model },
        },
        createdAt: now,
      },
    });

    return { generated: true, reason: 'generated' };
  }

  private async evaluateGenerationGate(
    summary: XiaobaoRiskSummaryPayload,
    now: Date,
  ): Promise<
    | { allow: true; escalated: boolean }
    | { allow: false; reason: 'policy_skip' | 'cache_hit' | 'cooldown' }
  > {
    if (!shouldRequestRiskInsight(summary)) {
      return { allow: false, reason: 'policy_skip' };
    }

    const reusable = await this.findReusableInsight(summary);
    if (reusable) return { allow: false, reason: 'cache_hit' };

    const latest = await this.findLatestInsight(summary.versionId);
    const escalated = latest ? isRiskLevelEscalation(summary.riskLevel, latest.riskSignature) : false;
    if (latest && !escalated && isWithinCooldown(latest, now)) {
      return { allow: false, reason: 'cooldown' };
    }

    return { allow: true, escalated };
  }

  private findReusableInsight(summary: XiaobaoRiskSummaryPayload): Promise<any | null> {
    return this.prisma.xiaobaoRiskInsight.findFirst({
      where: {
        versionId: summary.versionId,
        riskSignature: summary.riskSignature,
        status: 'generated',
      },
      orderBy: { createdAt: 'desc' },
    });
  }

  private findLatestInsight(versionId: string): Promise<any | null> {
    return this.prisma.xiaobaoRiskInsight.findFirst({
      where: {
        versionId,
        status: 'generated',
      },
      orderBy: { createdAt: 'desc' },
    });
  }
}

function buildRiskInterpretRequest(summary: XiaobaoRiskSummaryPayload): XiaobaoAiRiskInterpretRequest {
  return {
    versionId: summary.versionId,
    versionName: summary.versionName,
    productName: summary.productName,
    projectName: summary.projectName,
    riskScore: summary.riskScore,
    riskLevel: summary.riskLevel,
    expectedReleaseDate: summary.expectedReleaseDate,
    forecastReleaseDate: summary.forecastReleaseDate,
    delayDays: summary.delayDays,
    confidence: summary.confidence,
    signals: summary.signals,
    trendSummary: buildTrendSummary(summary),
    reasons: summary.reasons.map((reason) => ({
      key: reason.key,
      label: reason.title,
      severity: mapReasonSeverity(reason),
      detail: reason.detail,
    })),
    silentRisks: summarizeSilentRisks(summary.dailyEvidence.silentRisks),
    dailyEvidence: {
      todayDeliveries: summarizeEvidence(summary.dailyEvidence.todayDeliveries),
      todayProgress: summarizeEvidence(summary.dailyEvidence.todayProgress),
      todayCreations: summarizeEvidence(summary.dailyEvidence.todayCreations),
      todayRisks: summarizeEvidence(summary.dailyEvidence.todayRisks),
      progressNotes: summarizeEvidence(summary.dailyEvidence.progressNotes),
      needsProgressItems: summarizeEvidence(summary.dailyEvidence.needsProgressItems),
      recentActivityCount: summary.dailyEvidence.recentActivityCount,
      totalActivityCount: summary.dailyEvidence.totalActivityCount,
      todayActualHours: summary.dailyEvidence.todayActualHours,
      lastActivityAt: summary.dailyEvidence.lastActivityAt,
    },
  };
}

function shouldRequestRiskInsight(summary: XiaobaoRiskSummaryPayload): boolean {
  if (summary.riskLevel === 'on_track') return false;
  if (summary.riskLevel === 'at_risk' || summary.riskLevel === 'likely_delayed' || summary.riskLevel === 'blocked') {
    return true;
  }

  return (
    summary.riskLevel === 'attention'
    && summary.signals.daysToExpectedRelease !== undefined
    && summary.signals.daysToExpectedRelease <= 1
    && summary.signals.unfinishedCount > 0
  );
}

function isWithinCooldown(latest: any, now: Date): boolean {
  const createdAt = toDate(latest?.createdAt) ?? toDate(latest?.updatedAt);
  if (!createdAt) return false;
  return now.getTime() - createdAt.getTime() < XIAOBAO_AI_COOLDOWN_MS;
}

function isRiskLevelEscalation(currentLevel: XiaobaoRiskLevel, latestSignature: string): boolean {
  const previousLevel = parseRiskLevel(latestSignature);
  if (!previousLevel) return false;
  return XIAOBAO_RISK_LEVEL_RANK[currentLevel] > XIAOBAO_RISK_LEVEL_RANK[previousLevel];
}

function parseRiskLevel(signature: string): XiaobaoRiskLevel | undefined {
  try {
    const parsed = JSON.parse(signature) as { riskLevel?: XiaobaoRiskLevel };
    if (parsed.riskLevel && parsed.riskLevel in XIAOBAO_RISK_LEVEL_RANK) return parsed.riskLevel;
  } catch {
    // Fall through to pipe-string compatibility.
  }

  const parts = signature.split('|');
  return parts.find((part): part is XiaobaoRiskLevel => part in XIAOBAO_RISK_LEVEL_RANK);
}

function buildDedupeKey(versionId: string, riskSignature: string): string {
  return `${versionId}:${riskSignature}`;
}

function buildTrendSummary(summary: XiaobaoRiskSummaryPayload): string {
  if (summary.delayDays > 0) {
    return `当前风险等级 ${summary.riskLevel}，风险分 ${summary.riskScore}，预计延期 ${summary.delayDays} 天。`;
  }
  return `当前风险等级 ${summary.riskLevel}，风险分 ${summary.riskScore}。`;
}

function mapReasonSeverity(reason: XiaobaoRiskSummaryPayload['reasons'][number]): XiaobaoAiRiskInterpretRequest['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: unknown[]): string[] {
  return items.map(summarizeEvidenceItem).filter((item) => item.length > 0);
}

function summarizeEvidenceItem(item: unknown): string {
  if (typeof item === 'string') return item.trim();
  if (!isRecord(item)) return '';
  const summary = readString(item.summary);
  if (summary) return summary;
  const title = readString(item.title);
  const detail = readString(item.detail);
  return [title, detail].filter(Boolean).join('：').trim();
}

function summarizeSilentRisks(items: unknown[]): XiaobaoAiRiskInterpretRequest['silentRisks'] {
  return items
    .map((item) => {
      if (!isRecord(item)) return null;
      const key = readString(item.key);
      const detail = readString(item.detail) || readString(item.summary);
      if (!key || !detail) return null;
      const days = readNumber(item.days);
      return days === undefined ? { key, detail } : { key, detail, days };
    })
    .filter((item): item is XiaobaoAiRiskInterpretRequest['silentRisks'][number] => Boolean(item));
}

function coerceSummary(row: any): XiaobaoRiskSummaryPayload {
  const source = isRecord(row?.summary) ? row.summary : {};
  const versionId = readString(source.versionId) ?? String(row.versionId);
  const riskLevel = readRiskLevel(source.riskLevel) ?? readRiskLevel(row.riskLevel) ?? 'attention';
  const riskScore = readNumber(source.riskScore) ?? readNumber(row.riskScore) ?? 0;
  const confidence = readNumber(source.confidence) ?? readNumber(row.confidence) ?? 0;
  const riskSignature = readString(source.riskSignature) ?? String(row.riskSignature ?? `${versionId}|${riskLevel}|${riskScore}`);
  const dailyEvidence = readRecord(source.dailyEvidence);
  const signals = readRecord(source.signals);
  const currentSnapshot = readRecord(source.currentSnapshot);

  return {
    versionId,
    versionName: readString(source.versionName) ?? versionId,
    productId: readString(source.productId) ?? undefined,
    productName: readString(source.productName) ?? undefined,
    projectId: readString(source.projectId),
    projectName: readString(source.projectName) ?? undefined,
    expectedReleaseDate: readString(source.expectedReleaseDate) ?? null,
    riskScore,
    riskLevel,
    confidence,
    forecastReleaseDate: readString(source.forecastReleaseDate) ?? undefined,
    delayDays: readNumber(source.delayDays) ?? 0,
    remainingWorkHours: readNumber(source.remainingWorkHours) ?? 0,
    riskSignature,
    signals: {
      unfinishedCount: readNumber(signals.unfinishedCount) ?? 0,
      openBugCount: readNumber(signals.openBugCount) ?? 0,
      criticalBugCount: readNumber(signals.criticalBugCount) ?? 0,
      failedTestCount: readNumber(signals.failedTestCount) ?? 0,
      blockedCount: readNumber(signals.blockedCount) ?? 0,
      silentRiskCount: readNumber(signals.silentRiskCount) ?? 0,
      daysToExpectedRelease: readNumber(signals.daysToExpectedRelease),
    },
    reasons: readReasons(source.reasons),
    dailyEvidence: {
      todayDeliveries: readArray(dailyEvidence.todayDeliveries),
      todayProgress: readArray(dailyEvidence.todayProgress),
      todayCreations: readArray(dailyEvidence.todayCreations),
      todayRisks: readArray(dailyEvidence.todayRisks),
      progressNotes: readArray(dailyEvidence.progressNotes),
      needsProgressItems: readArray(dailyEvidence.needsProgressItems),
      recentActivityCount: readNumber(dailyEvidence.recentActivityCount) ?? 0,
      totalActivityCount: readNumber(dailyEvidence.totalActivityCount) ?? 0,
      todayActualHours: readNumber(dailyEvidence.todayActualHours) ?? 0,
      lastActivityAt: readString(dailyEvidence.lastActivityAt) ?? undefined,
      silentRisks: readArray(dailyEvidence.silentRisks),
    },
    currentSnapshot: {
      versionId,
      date: readString(currentSnapshot.date) ?? new Date().toISOString().slice(0, 10),
      riskScore,
      riskLevel,
      forecastReleaseDate: readString(currentSnapshot.forecastReleaseDate) ?? undefined,
      openBugCount: readNumber(currentSnapshot.openBugCount) ?? 0,
      criticalBugCount: readNumber(currentSnapshot.criticalBugCount) ?? 0,
      failedTestCount: readNumber(currentSnapshot.failedTestCount) ?? 0,
      blockedCount: readNumber(currentSnapshot.blockedCount) ?? 0,
      silentRiskCount: readNumber(currentSnapshot.silentRiskCount) ?? 0,
      confidence,
      createdAt: readString(currentSnapshot.createdAt) ?? new Date().toISOString(),
    },
  };
}

function readReasons(value: unknown): XiaobaoRiskSummaryPayload['reasons'] {
  return readArray(value)
    .map((item) => {
      if (!isRecord(item)) return null;
      const key = readString(item.key);
      const title = readString(item.title) ?? readString(item.label);
      const detail = readString(item.detail);
      const severity = readSummarySeverity(item.severity);
      if (!key || !title || !detail || !severity) return null;
      const count = readNumber(item.count);
      return count === undefined ? { key, title, detail, severity } : { key, title, detail, severity, count };
    })
    .filter((item): item is XiaobaoRiskSummaryPayload['reasons'][number] => Boolean(item));
}

function readSummarySeverity(value: unknown): XiaobaoRiskSummaryPayload['reasons'][number]['severity'] | undefined {
  return value === 'info' || value === 'warning' || value === 'danger' ? value : undefined;
}

function readRiskLevel(value: unknown): XiaobaoRiskLevel | undefined {
  return typeof value === 'string' && value in XIAOBAO_RISK_LEVEL_RANK ? value as XiaobaoRiskLevel : undefined;
}

function toDate(value: unknown): Date | undefined {
  if (value instanceof Date && Number.isFinite(value.getTime())) return value;
  if (typeof value !== 'string') return undefined;
  const date = new Date(value);
  return Number.isFinite(date.getTime()) ? date : undefined;
}

function readArray(value: unknown): unknown[] {
  return Array.isArray(value) ? value : [];
}

function readRecord(value: unknown): Record<string, unknown> {
  return isRecord(value) ? value : {};
}

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

function readString(value: unknown): string | undefined {
  return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
}

function readNumber(value: unknown): number | undefined {
  return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
}
