import path from 'path';
import * as tf from '@tensorflow/tfjs';
import '@tensorflow/tfjs-backend-cpu';
// nobundle + cpu backend — ไม่ใช้ tfjs-node (Windows/Docker friendly)
import * as faceapi from '@vladmandic/face-api/dist/face-api.esm-nobundle.js';
import canvas from 'canvas';
import { isFaceVerifyEnabled } from '../config/faceVerify';
import { loadStudentReferenceBuffer } from './studentReferenceImage.service';

const { Canvas, Image, ImageData } = canvas;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
(faceapi.env as any).monkeyPatch({ Canvas, Image, ImageData });

let modelsReady = false;
let modelsLoading: Promise<void> | null = null;

const MATCH_DISTANCE = 0.6;

function modelsDir(): string {
  return (
    process.env.FACE_MODEL_PATH ||
    path.resolve(process.cwd(), 'models', 'face-api')
  );
}

export async function ensureFaceModelsLoaded(): Promise<boolean> {
  if (!isFaceVerifyEnabled()) return false;
  if (modelsReady) return true;
  if (!modelsLoading) {
    modelsLoading = (async () => {
      await tf.setBackend('cpu');
      await tf.ready();
      const dir = modelsDir();
      await faceapi.nets.ssdMobilenetv1.loadFromDisk(dir);
      await faceapi.nets.faceLandmark68Net.loadFromDisk(dir);
      await faceapi.nets.faceRecognitionNet.loadFromDisk(dir);
      modelsReady = true;
      console.log(`✅ Face verify models loaded from ${dir}`);
    })().catch((err) => {
      modelsLoading = null;
      console.error('❌ Face verify model load failed:', err);
      throw err;
    });
  }
  await modelsLoading;
  return modelsReady;
}

export function faceModelsReady(): boolean {
  return modelsReady;
}

async function loadImageFromBuffer(buffer: Buffer): Promise<canvas.Image> {
  return canvas.loadImage(buffer);
}

async function largestFaceDescriptor(image: canvas.Image) {
  const detections = await faceapi
    .detectAllFaces(image as unknown as HTMLCanvasElement)
    .withFaceLandmarks()
    .withFaceDescriptors();

  if (!detections.length) return null;

  let best = detections[0];
  for (const d of detections.slice(1)) {
    const area = d.detection.box.width * d.detection.box.height;
    const bestArea = best.detection.box.width * best.detection.box.height;
    if (area > bestArea) best = d;
  }
  return best.descriptor;
}

/** distance 0 = เหมือนมาก, ~0.6 = เกณฑ์ match ของ face-api */
export function distanceToSimilarityPercent(distance: number): number {
  const pct = (1 - distance / MATCH_DISTANCE) * 100;
  return Math.round(Math.max(0, Math.min(100, pct)));
}

export async function compareFaceBuffers(
  liveBuffer: Buffer,
  referenceBuffer: Buffer,
): Promise<{
  similarity: number | null;
  distance: number | null;
  passed: boolean;
  liveFaces: number;
  referenceFaces: number;
  message: string;
}> {
  await ensureFaceModelsLoaded();

  const liveImg = await loadImageFromBuffer(liveBuffer);
  const refImg = await loadImageFromBuffer(referenceBuffer);

  const liveDetections = await faceapi.detectAllFaces(
    liveImg as unknown as HTMLCanvasElement,
  );
  const refDetections = await faceapi.detectAllFaces(
    refImg as unknown as HTMLCanvasElement,
  );

  if (!liveDetections.length) {
    return {
      similarity: null,
      distance: null,
      passed: true,
      liveFaces: 0,
      referenceFaces: refDetections.length,
      message: 'ไม่พบใบหน้าในกล้อง — ข้ามรอบนี้',
    };
  }

  if (!refDetections.length) {
    return {
      similarity: null,
      distance: null,
      passed: true,
      liveFaces: liveDetections.length,
      referenceFaces: 0,
      message: 'ไม่พบใบหน้าในรูปอ้างอิง — ข้ามรอบนี้',
    };
  }

  const liveDesc = await largestFaceDescriptor(liveImg);
  const refDesc = await largestFaceDescriptor(refImg);

  if (!liveDesc || !refDesc) {
    return {
      similarity: null,
      distance: null,
      passed: true,
      liveFaces: liveDetections.length,
      referenceFaces: refDetections.length,
      message: 'สกัดใบหน้าไม่สำเร็จ — ข้ามรอบนี้',
    };
  }

  const distance = faceapi.euclideanDistance(liveDesc, refDesc);
  const similarity = distanceToSimilarityPercent(distance);

  return {
    similarity,
    distance,
    passed: true,
    liveFaces: liveDetections.length,
    referenceFaces: refDetections.length,
    message: `ความคล้าย ${similarity}%`,
  };
}

export async function compareLiveSnapshotWithStudentPhoto(
  liveBuffer: Buffer,
  userId: string,
): Promise<{
  similarity: number | null;
  distance: number | null;
  passed: boolean;
  liveFaces: number;
  referenceFaces: number;
  message: string;
}> {
  const refBuf = await loadStudentReferenceBuffer(userId);
  return compareFaceBuffers(liveBuffer, refBuf);
}
