<?php

namespace App\Modules\Face\Services;

use CodeIgniter\HTTP\CURLRequest;

/**
 * FaceService
 *
 * Bertugas berkomunikasi dengan engine embedding wajah eksternal (Python/ONNX),
 * serta menyediakan helper cosine similarity.
 *
 * Konfigurasi ENV:
 * - FACE_SERVICE_URL   (contoh: http://localhost:5000)
 * - FACE_EMBEDDING_DIM (default: 512)
 * - FACE_SIM_THRESHOLD (default: 0.85)
 */
class FaceService
{
    protected CURLRequest $http;
    protected string $serviceUrl;
    protected int $embeddingDim;
    protected float $defaultThreshold;

    public function __construct()
    {
        /** @var CURLRequest $http */
        $http = service('curlrequest');
        $this->http           = $http;
        $this->serviceUrl     = rtrim((string) (env('FACE_SERVICE_URL') ?? 'http://localhost:5000'), '/');
        $this->embeddingDim   = (int) (env('FACE_EMBEDDING_DIM') ?? 512);
        $this->defaultThreshold = (float) (env('FACE_SIM_THRESHOLD') ?? 0.85);
    }

    /**
     * Kirim gambar ke service eksternal untuk mendapatkan embedding + metrik kualitas.
     * Expected response JSON:
     * {
     *   "embedding": [float...],
     *   "quality_score": float,
     *   "faces_count": int,
     *   "face_size": float,
     *   "blur": float,
     *   "brightness": float
     * }
     *
     * @throws \RuntimeException jika gagal atau quality gate tidak lolos
     */
    public function extractEmbeddingWithQuality(string $imagePath): array
    {
        if (! is_file($imagePath)) {
            throw new \RuntimeException('File gambar tidak ditemukan: ' . $imagePath);
        }

        $url = $this->serviceUrl . '/embed';

        $response = $this->http->post($url, [
            'multipart' => [
                [
                    'name'     => 'image',
                    'contents' => fopen($imagePath, 'rb'),
                    'filename' => basename($imagePath),
                ],
            ],
            'timeout' => 15,
        ]);

        $status = $response->getStatusCode();
        if ($status !== 200) {
            throw new \RuntimeException('Face service HTTP error: ' . $status);
        }

        $data = json_decode($response->getBody(), true);
        if (! is_array($data) || empty($data['embedding']) || ! is_array($data['embedding'])) {
            throw new \RuntimeException('Face service response invalid');
        }

        $embedding = array_map('floatval', $data['embedding']);
        if (count($embedding) !== $this->embeddingDim) {
            throw new \RuntimeException('Embedding dimensi tidak sesuai: ' . count($embedding));
        }

        $facesCount = (int) ($data['faces_count'] ?? 0);
        $faceSize   = (float) ($data['face_size'] ?? 0);
        $blur       = (float) ($data['blur'] ?? 0);
        $brightness = (float) ($data['brightness'] ?? 0);

        // Quality gates (bisa di-tune lewat ENV nanti)
        if ($facesCount !== 1) {
            throw new \RuntimeException('Foto harus mengandung tepat 1 wajah (faces_count=' . $facesCount . ')');
        }
        if ($faceSize < (float) (env('FACE_MIN_SIZE', 80))) {
            throw new \RuntimeException('Wajah terlalu kecil untuk verifikasi');
        }
        if ($blur < (float) (env('FACE_MIN_BLUR', 30))) {
            throw new \RuntimeException('Foto terlalu blur, silakan ulangi');
        }
        if ($brightness < (float) (env('FACE_MIN_BRIGHTNESS', 0.2))) {
            throw new \RuntimeException('Foto terlalu gelap, silakan cari cahaya lebih terang');
        }

        return [
            'embedding'     => $embedding,
            'quality_score' => (float) ($data['quality_score'] ?? 1.0),
        ];
    }

    /**
     * Cosine similarity antara dua embedding.
     *
     * @param float[] $a
     * @param float[] $b
     */
    public function cosineSimilarity(array $a, array $b): float
    {
        if (count($a) !== count($b) || count($a) === 0) {
            return 0.0;
        }

        $dot = 0.0;
        $na  = 0.0;
        $nb  = 0.0;
        $n   = count($a);
        for ($i = 0; $i < $n; $i++) {
            $va = (float) $a[$i];
            $vb = (float) $b[$i];
            $dot += $va * $vb;
            $na  += $va * $va;
            $nb  += $vb * $vb;
        }

        if ($na <= 0.0 || $nb <= 0.0) {
            return 0.0;
        }

        return $dot / (sqrt($na) * sqrt($nb));
    }

    public function getDefaultThreshold(): float
    {
        return $this->defaultThreshold;
    }
}

