"""
==================================================
Smart Attendance AI
Similarity Service
==================================================
Provides utilities for comparing face embeddings.
==================================================
"""

from typing import List, Tuple

import numpy as np

from config.settings import settings


class SimilarityService:

    # ==========================================
    # Normalize Embedding
    # ==========================================

    @staticmethod
    def normalize(
        embedding: List[float]
    ) -> np.ndarray:
        """
        L2 normalize an embedding vector.
        """

        vector = np.asarray(
            embedding,
            dtype=np.float32
        )

        norm = np.linalg.norm(vector)

        if norm == 0:
            return vector

        return vector / norm

    # ==========================================
    # Cosine Similarity
    # ==========================================

    @staticmethod
    def cosine_similarity(
        embedding1: List[float],
        embedding2: List[float]
    ) -> float:
        """
        Compute cosine similarity between
        two embeddings.
        """

        emb1 = SimilarityService.normalize(
            embedding1
        )

        emb2 = SimilarityService.normalize(
            embedding2
        )

        similarity = np.dot(
            emb1,
            emb2
        )

        return float(similarity)

    # ==========================================
    # Euclidean Distance
    # ==========================================

    @staticmethod
    def euclidean_distance(
        embedding1: List[float],
        embedding2: List[float]
    ) -> float:
        """
        Compute Euclidean distance between
        two embeddings.
        """

        emb1 = np.asarray(
            embedding1,
            dtype=np.float32
        )

        emb2 = np.asarray(
            embedding2,
            dtype=np.float32
        )

        distance = np.linalg.norm(
            emb1 - emb2
        )

        return float(distance)

    # ==========================================
    # Match Decision
    # ==========================================

    @staticmethod
    def is_match(
        similarity: float
    ) -> bool:
        """
        Determine whether two embeddings
        belong to the same person.
        """

        return (
            similarity >=
            settings.SIMILARITY_THRESHOLD
        )

    # ==========================================
    # Best Match
    # ==========================================

    @staticmethod
    def best_match(
        query_embedding: List[float],
        embeddings: List[List[float]]
    ) -> Tuple[int, float]:
        """
        Returns:
            (best_index, similarity)
        """

        best_index = -1
        best_similarity = -1.0

        for index, embedding in enumerate(
            embeddings
        ):

            similarity = (
                SimilarityService.cosine_similarity(
                    query_embedding,
                    embedding
                )
            )

            if similarity > best_similarity:

                best_similarity = similarity

                best_index = index

        return (
            best_index,
            best_similarity
        )