import prisma from "@shieldai/db";
import { AudioPreprocessor, AudioFeatures } from "../preprocessor/AudioPreprocessor";
import { EmbeddingService, EmbeddingOutput } from "../embedding/EmbeddingService";
import { VoiceEnrollmentService } from "../enrollment/VoiceEnrollmentService";
import { emitVoicePrintAlert } from "@shieldai/correlation";
import {
  AnalyzeAudioInput,
  AnalysisJobStatus,
  AnalysisType,
  DetectionVerdict,
  AnalysisResultOutput,
} from "@shieldai/types";

export class AnalysisService {
  private preprocessor: AudioPreprocessor;
  private embeddingService: EmbeddingService;
  private enrollmentService: VoiceEnrollmentService;
  private readonly syntheticThreshold = 0.7;
  private readonly uncertainThreshold = 0.4;

  constructor() {
    this.preprocessor = new AudioPreprocessor();
    this.embeddingService = new EmbeddingService();
    this.enrollmentService = new VoiceEnrollmentService();
  }

  async analyze(input: AnalyzeAudioInput, userId: string): Promise<AnalysisResultOutput> {
    const startTime = Date.now();

    const job = await prisma.analysisJob.create({
      data: {
        userId,
        analysisType: input.analysisType || AnalysisType.SYNTHETIC_DETECTION,
        audioFilePath: `voiceprint/${userId}/${Date.now()}.wav`,
        status: AnalysisJobStatus.RUNNING,
      },
    });

    try {
      const preprocessed = await this.preprocessor.preprocess(input.audioBuffer, input.sampleRate);
      const features = await this.preprocessor.extractFeatures(preprocessed.audio);
      const embedding = await this.embeddingService.extract(preprocessed.audio);

      const syntheticScore = await this.classifySynthetic(features, embedding);
      const verdict = this.determineVerdict(syntheticScore);
      const confidence = this.computeConfidence(syntheticScore, verdict);

      let matchedEnrollmentId: string | undefined;
      let matchedSimilarity: number | undefined;

      if (input.analysisType === AnalysisType.VOICE_MATCH) {
        const match = await this.enrollmentService.matchVoice(input.audioBuffer, userId);
        if (match) {
          matchedEnrollmentId = match.enrollmentId;
          matchedSimilarity = match.similarity;
        }
      }

      const processingTimeMs = Date.now() - startTime;

      const result = await prisma.analysisResult.create({
        data: {
          analysisJobId: job.id,
          syntheticScore,
          verdict,
          confidence,
          processingTimeMs,
          matchedEnrollmentId,
          matchedSimilarity,
          modelVersion: this.embeddingService.getModelVersion(),
        },
      });

      await prisma.analysisJob.update({
        where: { id: job.id },
        data: {
          status: AnalysisJobStatus.COMPLETED,
          completedAt: new Date(),
        },
      });

      if (result.verdict === DetectionVerdict.SYNTHETIC || result.verdict === DetectionVerdict.UNCERTAIN) {
        emitVoicePrintAlert(
          userId,
          job.id,
          result.verdict,
          result.syntheticScore,
          result.confidence,
          result.matchedEnrollmentId || undefined,
          result.matchedSimilarity || undefined,
          input.analysisType || undefined
        ).catch((err) => console.error(`[Correlation] VoicePrint emit failed:`, err));
      }

      return {
        jobId: job.id,
        syntheticScore: result.syntheticScore,
        verdict: result.verdict,
        confidence: result.confidence,
        matchedEnrollmentId: result.matchedEnrollmentId || undefined,
        matchedSimilarity: result.matchedSimilarity || undefined,
        processingTimeMs: result.processingTimeMs,
        modelVersion: result.modelVersion || undefined,
      };
    } catch (err) {
      const message = err instanceof Error ? err.message : "Analysis failed";
      await prisma.analysisJob.update({
        where: { id: job.id },
        data: {
          status: AnalysisJobStatus.FAILED,
          errorMessage: message,
          completedAt: new Date(),
        },
      });
      throw err;
    }
  }

  async getResult(jobId: string): Promise<AnalysisResultOutput | null> {
    const job = await prisma.analysisJob.findUnique({
      where: { id: jobId },
      include: { result: true },
    });

    if (!job || !job.result) return null;

    const r = job.result;
    return {
      jobId,
      syntheticScore: r.syntheticScore,
      verdict: r.verdict,
      confidence: r.confidence,
      matchedEnrollmentId: r.matchedEnrollmentId || undefined,
      matchedSimilarity: r.matchedSimilarity || undefined,
      processingTimeMs: r.processingTimeMs,
      modelVersion: r.modelVersion || undefined,
    };
  }

  async getUserResults(userId: string, limit: number = 20): Promise<AnalysisResultOutput[]> {
    const jobs = await prisma.analysisJob.findMany({
      where: { userId, status: AnalysisJobStatus.COMPLETED },
      orderBy: { createdAt: "desc" },
      take: limit,
      include: { result: true },
    });

    return jobs
      .filter((j) => j.result)
      .map((j) => {
        const r = j.result!;
        return {
          jobId: j.id,
          syntheticScore: r.syntheticScore,
          verdict: r.verdict,
          confidence: r.confidence,
          matchedEnrollmentId: r.matchedEnrollmentId || undefined,
          matchedSimilarity: r.matchedSimilarity || undefined,
          processingTimeMs: r.processingTimeMs,
          modelVersion: r.modelVersion || undefined,
        };
      });
  }

  private async classifySynthetic(
    features: AudioFeatures,
    embedding: EmbeddingOutput
  ): Promise<number> {
    const modelScore = await this.embeddingService.classify(embedding.vector);

    const zcrAnomaly = Math.abs(features.zeroCrossingRate - 0.05) / 0.05;
    const spectralAnomaly = Math.abs(features.spectralCentroid - 500) / 500;

    const artifactScore = Math.min(1, (zcrAnomaly + spectralAnomaly) / 4);

    return 0.7 * modelScore + 0.3 * artifactScore;
  }

  private determineVerdict(score: number): DetectionVerdict {
    if (score >= this.syntheticThreshold) return DetectionVerdict.SYNTHETIC;
    if (score <= this.uncertainThreshold) return DetectionVerdict.NATURAL;
    return DetectionVerdict.UNCERTAIN;
  }

  private computeConfidence(score: number, verdict: DetectionVerdict): number {
    if (verdict === DetectionVerdict.SYNTHETIC) {
      return Math.min(1, (score - this.syntheticThreshold) / (1 - this.syntheticThreshold));
    }
    if (verdict === DetectionVerdict.NATURAL) {
      return Math.min(1, (this.uncertainThreshold - score) / this.uncertainThreshold);
    }
    return 1 - Math.min(
      Math.abs(score - this.uncertainThreshold) / (this.syntheticThreshold - this.uncertainThreshold),
      1
    );
  }
}
