import { Injectable } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { QAFeedback } from '@prisma/client';
import { RagflowService } from './ragflow.service';

export interface SourceDocument {
  id: string;
  type: 'document' | 'article';
  title: string;
  webUrl?: string;
  snippet: string;
  authorityLevel: string;
  docType: string;
  relevanceScore: number;
}

export interface AskResult {
  id: string;
  answer: string;
  sources: SourceDocument[];
  confidence: number;
  metadata: {
    modelUsed: string;
    tokensUsed: number;
    cost: number;
    responseTimeMs: number;
  };
}

@Injectable()
export class KnowledgeQaService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly ragflowService: RagflowService,
  ) {}

  async ask(question: string, userId: string): Promise<AskResult> {
    const start = Date.now();
    const datasetId = await this.ragflowService.resolveDatasetId();
    const chatId = await this.ragflowService.resolveChatId(datasetId);

    const response = await this.ragflowService.createChatCompletion(chatId, question);
    const message = response?.choices?.[0]?.message;
    const answer = message?.content || '抱歉，无法生成答案。';
    const reference = message?.reference;
    const referenceChunks = Array.isArray(reference)
      ? Object.fromEntries(reference.map((chunk, index) => [String(index), chunk]))
      : reference?.chunks ?? {};
    const sources = await this.buildSources(referenceChunks);
    const confidence = this.calculateConfidence(sources);
    const responseTimeMs = Date.now() - start;
    const tokensUsed = response?.usage?.total_tokens ?? 0;

    const logRecord = await this.prisma.aIQALog.create({
      data: {
        userId,
        question,
        answer,
        sourceDocuments: sources as any,
        confidence,
        modelUsed: response?.model ?? 'ragflow',
        tokensUsed,
        cost: 0,
        responseTimeMs,
      },
    });

    return {
      id: logRecord.id,
      answer,
      sources,
      confidence,
      metadata: {
        modelUsed: response?.model ?? 'ragflow',
        tokensUsed,
        cost: 0,
        responseTimeMs,
      },
    };
  }

  async submitFeedback(
    qaLogId: string,
    feedback: QAFeedback,
    comment?: string,
  ): Promise<void> {
    await this.prisma.aIQALog.update({
      where: { id: qaLogId },
      data: {
        feedback,
        feedbackComment: comment,
      },
    });
  }

  async getUserHistory(
    userId: string,
    limit: number = 20,
  ): Promise<Array<{ id: string; question: string; answer: string; createdAt: Date }>> {
    const logs = await this.prisma.aIQALog.findMany({
      where: { userId },
      select: {
        id: true,
        question: true,
        answer: true,
        createdAt: true,
      },
      orderBy: { createdAt: 'desc' },
      take: limit,
    });

    return logs;
  }

  private async buildSources(referenceChunks: Record<string, any>): Promise<SourceDocument[]> {
    const docIds = new Set<string>();
    Object.values(referenceChunks).forEach((chunk: any) => {
      if (chunk?.document_id) {
        docIds.add(chunk.document_id);
      }
    });

    const mappings = await this.prisma.ragflowDocument.findMany({
      where: { ragflowDocumentId: { in: Array.from(docIds) } },
    });
    const mappingByDoc = new Map(mappings.map((m) => [m.ragflowDocumentId, m]));

    const spIds = mappings.filter((m) => m.sourceType === 'SP_DOCUMENT').map((m) => m.sourceId);
    const articleIds = mappings.filter((m) => m.sourceType === 'ARTICLE').map((m) => m.sourceId);

    const [spDocs, articles] = await Promise.all([
      spIds.length > 0
        ? this.prisma.sPDocumentIndex.findMany({ where: { id: { in: spIds } } })
        : [],
      articleIds.length > 0
        ? this.prisma.knowledgeArticle.findMany({ where: { id: { in: articleIds } } })
        : [],
    ]);

    const spMap = new Map(spDocs.map((doc) => [doc.id, doc]));
    const articleMap = new Map(articles.map((article) => [article.id, article]));

    const sources: SourceDocument[] = [];
    for (const chunk of Object.values(referenceChunks)) {
      if (!chunk?.document_id) continue;
      const mapping = mappingByDoc.get(chunk.document_id);
      if (!mapping) continue;

      if (mapping.sourceType === 'SP_DOCUMENT') {
        const doc = spMap.get(mapping.sourceId);
        if (!doc) continue;
        sources.push({
          id: doc.id,
          type: 'document',
          title: doc.title,
          webUrl: doc.webUrl ?? undefined,
          snippet: this.clipSnippet(chunk.content),
          authorityLevel: doc.docAuthorityLevel,
          docType: doc.docType,
          relevanceScore: chunk.similarity ?? 0,
        });
      } else {
        const article = articleMap.get(mapping.sourceId);
        if (!article) continue;
        sources.push({
          id: article.id,
          type: 'article',
          title: article.title,
          snippet: this.clipSnippet(chunk.content),
          authorityLevel: article.status === 'PUBLISHED' ? 'PUBLISHED' : 'DRAFT',
          docType: 'ARTICLE',
          relevanceScore: chunk.similarity ?? 0,
        });
      }
    }

    return sources;
  }

  private calculateConfidence(sources: SourceDocument[]): number {
    if (sources.length === 0) {
      return 0;
    }
    const total = sources.reduce((sum, src) => sum + (src.relevanceScore || 0), 0);
    return Math.min(1, Math.max(0, total / sources.length));
  }

  private clipSnippet(content?: string, maxLength: number = 200): string {
    if (!content) return '';
    const cleaned = content.replace(/\s+/g, ' ').trim();
    if (cleaned.length <= maxLength) {
      return cleaned;
    }
    return `${cleaned.substring(0, maxLength)}...`;
  }
}
