import { Injectable, Logger, ForbiddenException } from '@nestjs/common';
import * as crypto from 'crypto';
import { PrismaService } from '../../infrastructure/database/prisma.service';
import { JobDefinitionRegistry } from './job-definition-registry';

/**
 * M-AI-08-02: Learning Analysis Snapshot Builder
 *
 * 从可信服务端数据源聚合学习指标，构建最小化、可复现的快照。
 *
 * 窗口：行为 7 天 / 成绩 30 天（与 Legacy 一致）
 * 数据源：DailyLearningActivity, LearningSession, QuizAttempt,
 *         ReviewCard+ReviewLog, AiAnalysisResult, FocusItem,
 *         KnowledgeItem, UserLearningProfile
 */

const SNAPSHOT_SCHEMA_VERSION = 'learning-analysis-v1';
const BEHAVIOR_WINDOW_DAYS = 7;
const SCORE_WINDOW_DAYS = 30;

export interface LearningAnalysisSnapshot {
  schemaVersion: string;
  snapshot: {
    userId: string;
    triggerType: 'manual' | 'scheduled';
    operationId: string;
    windowStart: string;
    windowEnd: string;
    timezone: string;
    aggregationVersion: string;
    sourceCutoffAt: string;
    learningGoal?: string;
    qualityPreference?: string;
    dailyAvailableMinutes?: number;
    studyMetrics: {
      totalStudyDuration: number;
      activeDays: number;
      sessionCount: number;
      averageSessionDuration: number;
      completionRate: number;
    };
    reviewMetrics: {
      reviewDueCount: number;
      reviewCompletedCount: number;
      reviewAccuracy: number;
      overdueCount: number;
      retentionTrend: number;
    };
    quizMetrics: {
      quizCount: number;
      attemptCount: number;
      accuracy: number;
      accuracyByQuestionType: {
        choice?:  { answeredCount: number; correctCount: number; accuracy: number };
        fill?:    { answeredCount: number; correctCount: number; accuracy: number };
        judge?:   { answeredCount: number; correctCount: number; accuracy: number };
      };
    };
    activeRecallMetrics: {
      count: number;
      avgScore: number;
      weaknessCount: number;
    };
    feynmanMetrics: {
      count: number;
      weaknessCount: number;
      focusItemCount: number;
    };
    knowledgeProgress: {
      totalItems: number;
      completedItems: number;
      inProgressItems: number;
      weakItems: Array<{ id: string; title: string }>;
    };
    dataQuality: {
      availableSources: string[];
      missingSources: string[];
      sampleSize: number;
      coverageStart?: string;
      coverageEnd?: string;
      insufficientDataReasons: string[];
      overall: 'insufficient' | 'limited' | 'sufficient';
    };
    promptKey: string;
    promptVersion: string;
    modelTier: string;
    inputSchemaVersion: string;
    outputSchemaVersion: string;
    createdAt: string;
  };
}

export interface LearningAnalysisSnapshotInput {
  userId: string;
  triggerType: 'manual' | 'scheduled';
  operationId: string;
}

@Injectable()
export class LearningAnalysisSnapshotBuilder {
  private readonly logger = new Logger(LearningAnalysisSnapshotBuilder.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly registry: JobDefinitionRegistry,
  ) {}

  async build(input: LearningAnalysisSnapshotInput): Promise<LearningAnalysisSnapshot> {
    const def = this.registry.get('learning_analysis');
    const now = new Date();
    const behaviorStart = new Date(now.getTime() - BEHAVIOR_WINDOW_DAYS * 86400000);
    const scoreStart = new Date(now.getTime() - SCORE_WINDOW_DAYS * 86400000);

    // 校验用户存在
    const user = await this.prisma.user.findUnique({ where: { id: input.userId }, select: { id: true } });
    if (!user) throw new ForbiddenException(`User ${input.userId} not found`);

    // 加载档案
    const profile = await this.prisma.userLearningProfile.findUnique({ where: { userId: input.userId } });

    // 聚合各维度数据
    const studyMetrics = await this.aggregateStudyMetrics(input.userId, behaviorStart, now);
    const reviewMetrics = await this.aggregateReviewMetrics(input.userId, scoreStart, now);
    const quizMetrics = await this.aggregateQuizMetrics(input.userId, scoreStart, now);
    const activeRecallMetrics = await this.aggregateActiveRecallMetrics(input.userId, scoreStart, now);
    const feynmanMetrics = await this.aggregateFeynmanMetrics(input.userId, scoreStart, now);
    const knowledgeProgress = await this.aggregateKnowledgeProgress(input.userId);

    // 数据质量
    const dataQuality = this.buildDataQuality({
      study: studyMetrics.activeDays > 0,
      review: reviewMetrics.reviewCompletedCount > 0,
      quiz: quizMetrics.quizCount > 0,
      activeRecall: activeRecallMetrics.count > 0,
      feynman: feynmanMetrics.count > 0,
      knowledge: knowledgeProgress.totalItems > 0,
    });

    const windowEnd = now.toISOString().replace(/\.\d{3}Z$/, 'Z');
    const windowStartStr = behaviorStart.toISOString().replace(/\.\d{3}Z$/, 'Z');

    const snapshot: LearningAnalysisSnapshot = {
      schemaVersion: SNAPSHOT_SCHEMA_VERSION,
      snapshot: {
        userId: input.userId,
        triggerType: input.triggerType,
        operationId: input.operationId,
        windowStart: windowStartStr,
        windowEnd,
        timezone: 'Asia/Shanghai',
        aggregationVersion: '1.0.0',
        sourceCutoffAt: windowEnd,
        learningGoal: profile?.learningGoal ?? undefined,
        qualityPreference: profile?.qualityPreference ?? undefined,
        dailyAvailableMinutes: profile?.dailyAvailableMinutes ?? undefined,
        studyMetrics,
        reviewMetrics,
        quizMetrics,
        activeRecallMetrics,
        feynmanMetrics,
        knowledgeProgress,
        dataQuality,
        promptKey: def.prompt.promptKey,
        promptVersion: def.prompt.promptVersion,
        modelTier: def.model.modelTier,
        inputSchemaVersion: SNAPSHOT_SCHEMA_VERSION,
        outputSchemaVersion: def.output.schemaVersion,
        createdAt: now.toISOString().replace(/\.\d{3}Z$/, 'Z'),
      },
    };

    this.logger.log(
      `Built learning analysis snapshot: userId=${input.userId} ` +
      `activeDays=${studyMetrics.activeDays} quizAccuracy=${quizMetrics.accuracy} ` +
      `dataQuality=${dataQuality.overall}`,
    );

    return snapshot;
  }

  computeHash(snapshot: LearningAnalysisSnapshot): string {
    const serialized = JSON.stringify(snapshot.snapshot, Object.keys(snapshot.snapshot).sort());
    return crypto.createHash('sha256').update(serialized).digest('hex').substring(0, 16);
  }

  // ── Aggregators ──

  private async aggregateStudyMetrics(userId: string, start: Date, end: Date) {
    try {
      const activities = await this.prisma.dailyLearningActivity.findMany({
        where: { userId, activityDate: { gte: start, lte: end } },
        select: { durationSeconds: true, activeRecallCount: true, reviewCount: true, readingSeconds: true },
      });
      const totalDuration = activities.reduce((s, a) => s + (a.durationSeconds || 0), 0);
      return {
        totalStudyDuration: totalDuration,
        activeDays: activities.length,
        sessionCount: activities.reduce((s, a) => s + (a.activeRecallCount || 0) + (a.reviewCount || 0), 0),
        averageSessionDuration: activities.length > 0 ? Math.round(totalDuration / activities.length) : 0,
        completionRate: 0, // computed from MaterialReadingProgress if available
      };
    } catch { return { totalStudyDuration: 0, activeDays: 0, sessionCount: 0, averageSessionDuration: 0, completionRate: 0 }; }
  }

  private async aggregateReviewMetrics(userId: string, start: Date, end: Date) {
    try {
      const [dueCount, completedCount, reviews] = await Promise.all([
        this.prisma.reviewCard.count({ where: { userId, nextReviewAt: { lte: end }, deletedAt: null } }),
        this.prisma.reviewLog.count({ where: { userId, createdAt: { gte: start } } }),
        this.prisma.reviewLog.findMany({
          where: { userId, createdAt: { gte: start } },
          select: { rating: true },
          take: 200,
        }),
      ]);
      const ratings = reviews.map(r => parseInt(r.rating, 10)).filter(n => !isNaN(n));
      const accuracy = ratings.length > 0 ? ratings.filter(r => r >= 3).length / ratings.length : 0;
      return {
        reviewDueCount: dueCount,
        reviewCompletedCount: completedCount,
        reviewAccuracy: Math.round(accuracy * 100) / 100,
        overdueCount: 0,
        retentionTrend: 0,
      };
    } catch { return { reviewDueCount: 0, reviewCompletedCount: 0, reviewAccuracy: 0, overdueCount: 0, retentionTrend: 0 }; }
  }

  private async aggregateQuizMetrics(userId: string, start: Date, end: Date) {
    try {
      const [quizCount, attempts] = await Promise.all([
        this.prisma.quiz.count({ where: { userId } }),
        this.prisma.quizAttempt.findMany({
          where: { userId, startedAt: { gte: start } },
          select: { id: true, correctCount: true, totalQuestions: true },
          take: 50,
        }),
      ]);

      const accuracy = attempts.length > 0
        ? attempts.reduce((s, a) => s + (a.totalQuestions > 0 ? a.correctCount / a.totalQuestions : 0), 0) / attempts.length
        : 0;

      // M-QUIZ-01-05: 按题型维度统计正确率
      const accuracyByQuestionType = await this.aggregateByQuestionType(userId, start);

      return {
        quizCount,
        attemptCount: attempts.length,
        accuracy: Math.round(accuracy * 100) / 100,
        accuracyByQuestionType,
      };
    } catch {
      return { quizCount: 0, attemptCount: 0, accuracy: 0, accuracyByQuestionType: {} };
    }
  }

  /**
   * M-QUIZ-01-05: 按题型聚合正确率
   *
   * 数据源：QuizAttempt（已完成） + QuizAnswer（服务端写入的 isCorrect） + QuizQuestion.type（数据库真实值）
   * 不信任客户端传入的 type 或 isCorrect。
   * 无该题型数据时 → 不返回该 key（标记缺失），而非返回 accuracy: 0。
   */
  private async aggregateByQuestionType(
    userId: string,
    scoreStart: Date,
  ): Promise<Record<string, { answeredCount: number; correctCount: number; accuracy: number }>> {
    // 只统计已完成 Attempt 的 QuizAnswer
    const answers = await this.prisma.quizAnswer.findMany({
      where: {
        attempt: {
          userId,
          finishedAt: { not: null },
          startedAt: { gte: scoreStart },
        },
      },
      select: {
        isCorrect: true,
        question: {
          select: { type: true },
        },
      },
      take: 1000,
    });

    // 按 question.type 分组聚合
    const byType: Record<string, { answered: number; correct: number }> = {};

    for (const a of answers) {
      const type = a.question?.type;
      if (!type) continue; // 跳过无 type 数据
      if (!byType[type]) byType[type] = { answered: 0, correct: 0 };
      byType[type].answered++;
      if (a.isCorrect) byType[type].correct++;
    }

    // 构建输出：有数据则填充，无数据则不返回（标记缺失）
    const result: Record<string, { answeredCount: number; correctCount: number; accuracy: number }> = {};
    for (const [type, stats] of Object.entries(byType)) {
      if (stats.answered > 0) {
        result[type] = {
          answeredCount: stats.answered,
          correctCount: stats.correct,
          accuracy: Math.round((stats.correct / stats.answered) * 100) / 100,
        };
      }
    }

    return result;
  }

  private async aggregateActiveRecallMetrics(userId: string, start: Date, end: Date) {
    try {
      const results = await this.prisma.aiAnalysisResult.findMany({
        where: { userId, createdAt: { gte: start } },
        select: { masteryScore: true, weaknesses: true },
        take: 50,
      });
      const scores = results.filter(r => typeof r.masteryScore === 'number').map(r => r.masteryScore as number);
      const weaknessCount = results.reduce((s, r) => s + (Array.isArray(r.weaknesses) ? r.weaknesses.length : 0), 0);
      return {
        count: results.length,
        avgScore: scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0,
        weaknessCount,
      };
    } catch { return { count: 0, avgScore: 0, weaknessCount: 0 }; }
  }

  private async aggregateFeynmanMetrics(userId: string, start: Date, end: Date) {
    try {
      const [results, focusItems] = await Promise.all([
        this.prisma.aiAnalysisResult.findMany({
          where: { userId, createdAt: { gte: start } },
          select: { id: true, weaknesses: true },
          take: 50,
        }),
        this.prisma.focusItem.count({ where: { userId, source: 'ai-analysis', createdAt: { gte: start } } }),
      ]);
      const weaknessCount = results.reduce((s, r) => s + (Array.isArray(r.weaknesses) ? r.weaknesses.length : 0), 0);
      return { count: results.length, weaknessCount, focusItemCount: focusItems };
    } catch { return { count: 0, weaknessCount: 0, focusItemCount: 0 }; }
  }

  private async aggregateKnowledgeProgress(userId: string) {
    try {
      const items = await this.prisma.knowledgeItem.findMany({
        where: { userId, deletedAt: null, status: 'active' },
        select: { id: true, title: true },
        take: 200,
      });
      // Weak items: those with active FocusItems
      const focusItemKis = await this.prisma.focusItem.findMany({
        where: { userId, status: 'open', knowledgeItemId: { not: null } },
        select: { knowledgeItemId: true },
        take: 50,
      });
      const weakKiIds = new Set(focusItemKis.map(f => f.knowledgeItemId).filter(Boolean));
      return {
        totalItems: await this.prisma.knowledgeItem.count({ where: { userId, deletedAt: null, status: 'active' } }),
        completedItems: 0,
        inProgressItems: 0,
        weakItems: items.filter(i => weakKiIds.has(i.id)).map(i => ({ id: i.id, title: i.title })),
      };
    } catch { return { totalItems: 0, completedItems: 0, inProgressItems: 0, weakItems: [] }; }
  }

  // ── Data Quality ──

  private buildDataQuality(sources: Record<string, boolean>) {
    const available = Object.entries(sources).filter(([, v]) => v).map(([k]) => k);
    const missing = Object.entries(sources).filter(([, v]) => !v).map(([k]) => k);
    const reasons: string[] = [];
    if (available.length === 0) reasons.push('no_data_in_window');
    else if (available.length < 3) reasons.push('limited_data');

    return {
      availableSources: available,
      missingSources: missing,
      sampleSize: available.length,
      coverageStart: undefined,
      coverageEnd: undefined,
      insufficientDataReasons: reasons,
      overall: available.length === 0 ? 'insufficient' as const
        : available.length < 3 ? 'limited' as const
        : 'sufficient' as const,
    };
  }
}
