# Copyright 2025-2026 Yakhyokhuja Valikhujaev
# Author: Yakhyokhuja Valikhujaev
# GitHub: https://github.com/yakhyo


from __future__ import annotations

import numpy as np
import pytest

from uniface import (
    create_attribute_predictor,
    create_detector,
    create_landmarker,
    create_recognizer,
    list_available_detectors,
)
from uniface.attribute import AgeGender, FairFace
from uniface.constants import AgeGenderWeights, FairFaceWeights, RetinaFaceWeights, SCRFDWeights
from uniface.spoofing import MiniFASNet, create_spoofer


# create_detector tests
def test_create_detector_retinaface():
    """
    Test creating a RetinaFace detector using factory function.
    """
    detector = create_detector('retinaface')
    assert detector is not None, 'Failed to create RetinaFace detector'


def test_create_detector_scrfd():
    """
    Test creating a SCRFD detector using factory function.
    """
    detector = create_detector('scrfd')
    assert detector is not None, 'Failed to create SCRFD detector'


def test_create_detector_with_config():
    """
    Test creating detector with custom configuration.
    """
    detector = create_detector(
        'retinaface',
        model_name=RetinaFaceWeights.MNET_V2,
        confidence_threshold=0.8,
        nms_threshold=0.3,
    )
    assert detector is not None, 'Failed to create detector with custom config'


def test_create_detector_invalid_method():
    """
    Test that invalid detector method raises an error.
    """
    with pytest.raises((ValueError, KeyError)):
        create_detector('invalid_method')


def test_create_detector_scrfd_with_model():
    """
    Test creating SCRFD detector with specific model.
    """
    detector = create_detector('scrfd', model_name=SCRFDWeights.SCRFD_10G_KPS, confidence_threshold=0.5)
    assert detector is not None, 'Failed to create SCRFD with specific model'


# create_recognizer tests
def test_create_recognizer_arcface():
    """
    Test creating an ArcFace recognizer using factory function.
    """
    recognizer = create_recognizer('arcface')
    assert recognizer is not None, 'Failed to create ArcFace recognizer'


def test_create_recognizer_mobileface():
    """
    Test creating a MobileFace recognizer using factory function.
    """
    recognizer = create_recognizer('mobileface')
    assert recognizer is not None, 'Failed to create MobileFace recognizer'


def test_create_recognizer_sphereface():
    """
    Test creating a SphereFace recognizer using factory function.
    """
    recognizer = create_recognizer('sphereface')
    assert recognizer is not None, 'Failed to create SphereFace recognizer'


def test_create_recognizer_edgeface():
    """Test creating an EdgeFace recognizer using factory function."""
    recognizer = create_recognizer('edgeface')
    assert recognizer is not None, 'Failed to create EdgeFace recognizer'


def test_create_recognizer_invalid_method():
    """
    Test that invalid recognizer method raises an error.
    """
    with pytest.raises((ValueError, KeyError)):
        create_recognizer('invalid_method')


# create_landmarker tests
def test_create_landmarker():
    """
    Test creating a Landmark106 detector using factory function.
    """
    landmarker = create_landmarker('2d106det')
    assert landmarker is not None, 'Failed to create Landmark106 detector'


def test_create_landmarker_default():
    """
    Test creating landmarker with default parameters.
    """
    landmarker = create_landmarker()
    assert landmarker is not None, 'Failed to create default landmarker'


def test_create_landmarker_invalid_method():
    """
    Test that invalid landmarker method raises an error.
    """
    with pytest.raises((ValueError, KeyError)):
        create_landmarker('invalid_method')


# list_available_detectors tests
def test_list_available_detectors():
    """
    Test that list_available_detectors returns a dictionary.
    """
    detectors = list_available_detectors()

    assert isinstance(detectors, dict), 'Should return a dictionary of detectors'
    assert len(detectors) > 0, 'Should have at least one detector available'


def test_list_available_detectors_contents():
    """
    Test that list includes known detectors.
    """
    detectors = list_available_detectors()

    # Should include at least these detectors
    assert 'retinaface' in detectors, "Should include 'retinaface'"
    assert 'scrfd' in detectors, "Should include 'scrfd'"


# Integration tests
def test_detector_inference_from_factory():
    """
    Test that detector created from factory can perform inference.
    """
    detector = create_detector('retinaface')
    mock_image = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)

    faces = detector.detect(mock_image)
    assert isinstance(faces, list), 'Detector should return list of faces'


def test_recognizer_inference_from_factory():
    """
    Test that recognizer created from factory can perform inference.
    """
    recognizer = create_recognizer('arcface')
    mock_image = np.random.randint(0, 255, (112, 112, 3), dtype=np.uint8)

    embedding = recognizer.get_embedding(mock_image)
    assert embedding is not None, 'Recognizer should return embedding'
    assert embedding.shape == (1, 512), 'get_embedding should return (1, 512) with batch dimension'


def test_landmarker_inference_from_factory():
    """
    Test that landmarker created from factory can perform inference.
    """
    landmarker = create_landmarker('2d106det')
    mock_image = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)
    mock_bbox = [100, 100, 300, 300]

    landmarks = landmarker.get_landmarks(mock_image, mock_bbox)
    assert landmarks is not None, 'Landmarker should return landmarks'
    assert landmarks.shape == (106, 2), 'Should return 106 landmarks'


def test_multiple_detector_creation():
    """
    Test that multiple detectors can be created independently.
    """
    detector1 = create_detector('retinaface')
    detector2 = create_detector('scrfd')

    assert detector1 is not None
    assert detector2 is not None
    assert detector1 is not detector2, 'Should create separate instances'


def test_detector_with_different_configs():
    """
    Test creating multiple detectors with different configurations.
    """
    detector_high_thresh = create_detector('retinaface', confidence_threshold=0.9)
    detector_low_thresh = create_detector('retinaface', confidence_threshold=0.3)

    mock_image = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)

    faces_high = detector_high_thresh.detect(mock_image)
    faces_low = detector_low_thresh.detect(mock_image)

    # Both should work
    assert isinstance(faces_high, list)
    assert isinstance(faces_low, list)


def test_factory_returns_correct_types():
    """
    Test that factory functions return instances of the correct types.
    """
    from uniface import ArcFace, Landmark106, RetinaFace

    detector = create_detector('retinaface')
    recognizer = create_recognizer('arcface')
    landmarker = create_landmarker('2d106det')

    assert isinstance(detector, RetinaFace), 'Should return RetinaFace instance'
    assert isinstance(recognizer, ArcFace), 'Should return ArcFace instance'
    assert isinstance(landmarker, Landmark106), 'Should return Landmark106 instance'


# create_spoofer tests
def test_create_spoofer_default():
    """Test creating a spoofer with default parameters."""
    spoofer = create_spoofer()
    assert isinstance(spoofer, MiniFASNet), 'Should return MiniFASNet instance'


def test_create_spoofer_with_providers():
    """Test that create_spoofer forwards providers kwarg without TypeError."""
    spoofer = create_spoofer(providers=['CPUExecutionProvider'])
    assert isinstance(spoofer, MiniFASNet), 'Should return MiniFASNet instance'


# create_attribute_predictor tests
def test_create_attribute_predictor_age_gender():
    predictor = create_attribute_predictor(AgeGenderWeights.DEFAULT)
    assert isinstance(predictor, AgeGender), 'Should return AgeGender instance'


def test_create_attribute_predictor_fairface():
    predictor = create_attribute_predictor(FairFaceWeights.DEFAULT)
    assert isinstance(predictor, FairFace), 'Should return FairFace instance'


def test_create_attribute_predictor_invalid():
    with pytest.raises(ValueError, match='Unsupported attribute model'):
        create_attribute_predictor('invalid_model')
