# built-in dependencies
import os
import base64
import unittest
from unittest.mock import patch, MagicMock
from packaging import version

# 3rd party dependencies
import gdown
import numpy as np
import flask
from flask import Flask
import werkzeug

# project dependencies
from deepface.api.src.app import create_app
from deepface.api.src.modules.core import routes
from deepface.commons.logger import Logger

logger = Logger()

IMG1_SOURCE = (
    "https://raw.githubusercontent.com/serengil/deepface/refs/heads/master/tests/dataset/img1.jpg"
)
IMG2_SOURCE = (
    "https://raw.githubusercontent.com/serengil/deepface/refs/heads/master/tests/dataset/img2.jpg"
)
DUMMY_APP = Flask(__name__)


class TestVerifyEndpoint(unittest.TestCase):
    def setUp(self):
        download_test_images(IMG1_SOURCE)
        download_test_images(IMG2_SOURCE)
        app = create_app()
        app.config["DEBUG"] = True
        app.config["TESTING"] = True
        self.app = app.test_client()

    def test_tp_verify(self):
        data = {
            "img1": "dataset/img1.jpg",
            "img2": "dataset/img2.jpg",
        }
        response = self.app.post("/verify", json=data)
        assert response.status_code == 200
        result = response.json
        logger.debug(result)

        assert result.get("verified") is not None
        assert result.get("model") is not None
        assert result.get("similarity_metric") is not None
        assert result.get("detector_backend") is not None
        assert result.get("distance") is not None
        assert result.get("threshold") is not None
        assert result.get("facial_areas") is not None

        assert result.get("verified") is True

        logger.info("✅ true-positive verification api test is done")

    def test_tn_verify(self):
        data = {
            "img1": "dataset/img1.jpg",
            "img2": "dataset/img2.jpg",
        }
        response = self.app.post("/verify", json=data)
        assert response.status_code == 200
        result = response.json
        logger.debug(result)

        assert result.get("verified") is not None
        assert result.get("model") is not None
        assert result.get("similarity_metric") is not None
        assert result.get("detector_backend") is not None
        assert result.get("distance") is not None
        assert result.get("threshold") is not None
        assert result.get("facial_areas") is not None

        assert result.get("verified") is True

        logger.info("✅ true-negative verification api test is done")

    def test_represent(self):
        data = {
            "img": "dataset/img1.jpg",
        }
        response = self.app.post("/represent", json=data)
        assert response.status_code == 200
        result = response.json
        logger.debug(result)
        assert result.get("results") is not None
        assert isinstance(result["results"], list) is True
        assert len(result["results"]) > 0
        for i in result["results"]:
            assert i.get("embedding") is not None
            assert isinstance(i.get("embedding"), list) is True
            assert len(i.get("embedding")) == 4096
            assert i.get("face_confidence") is not None
            assert i.get("facial_area") is not None

        logger.info("✅ representation api test is done (for image path)")

    def test_represent_encoded(self):
        image_path = "dataset/img1.jpg"
        with open(image_path, "rb") as image_file:
            encoded_string = "data:image/jpeg;base64," + base64.b64encode(image_file.read()).decode(
                "utf8"
            )

        data = {"model_name": "Facenet", "detector_backend": "mtcnn", "img": encoded_string}

        response = self.app.post("/represent", json=data)
        assert response.status_code == 200
        result = response.json
        logger.debug(result)
        assert result.get("results") is not None
        assert isinstance(result["results"], list) is True
        assert len(result["results"]) > 0
        for i in result["results"]:
            assert i.get("embedding") is not None
            assert isinstance(i.get("embedding"), list) is True
            assert len(i.get("embedding")) == 128
            assert i.get("face_confidence") is not None
            assert i.get("facial_area") is not None

        logger.info("✅ representation api test is done (for encoded image)")

    def test_represent_url(self):
        data = {
            "model_name": "Facenet",
            "detector_backend": "mtcnn",
            "img": "https://github.com/serengil/deepface/blob/master/tests/dataset/couple.jpg?raw=true",
        }

        response = self.app.post("/represent", json=data)
        assert response.status_code == 200
        result = response.json
        logger.debug(result)
        assert result.get("results") is not None
        assert isinstance(result["results"], list) is True
        assert len(result["results"]) == 2  # 2 faces are in the image link
        for i in result["results"]:
            assert i.get("embedding") is not None
            assert isinstance(i.get("embedding"), list) is True
            assert len(i.get("embedding")) == 128
            assert i.get("face_confidence") is not None
            assert i.get("facial_area") is not None

        logger.info("✅ representation api test is done (for image url)")

    def test_analyze(self):
        data = {
            "img": "dataset/img1.jpg",
        }
        response = self.app.post("/analyze", json=data)
        assert response.status_code == 200
        result = response.json
        logger.debug(result)
        assert result.get("results") is not None
        assert isinstance(result["results"], list) is True
        assert len(result["results"]) > 0
        for i in result["results"]:
            assert i.get("age") is not None
            assert isinstance(i.get("age"), (int, float))
            assert i.get("dominant_gender") is not None
            assert i.get("dominant_gender") in ["Man", "Woman"]
            assert i.get("dominant_emotion") is not None
            assert i.get("dominant_race") is not None

        logger.info("✅ analyze api test is done")

    def test_analyze_inputformats(self):
        image_path = "dataset/couple.jpg"
        with open(image_path, "rb") as image_file:
            encoded_image = "data:image/jpeg;base64," + base64.b64encode(image_file.read()).decode(
                "utf8"
            )

        image_sources = [
            # image path
            image_path,
            # image url
            f"https://github.com/serengil/deepface/blob/master/tests/{image_path}?raw=true",
            # encoded image
            encoded_image,
        ]

        results = []
        for img in image_sources:
            data = {
                "img": img,
            }
            response = self.app.post("/analyze", json=data)

            assert response.status_code == 200
            result = response.json
            results.append(result)

            assert result.get("results") is not None
            assert isinstance(result["results"], list) is True
            assert len(result["results"]) > 0
            for i in result["results"]:
                assert i.get("age") is not None
                assert isinstance(i.get("age"), (int, float))
                assert i.get("dominant_gender") is not None
                assert i.get("dominant_gender") in ["Man", "Woman"]
                assert i.get("dominant_emotion") is not None
                assert i.get("dominant_race") is not None

        assert len(results[0]["results"]) == len(results[1]["results"]) and len(
            results[0]["results"]
        ) == len(results[2]["results"])

        for i in range(len(results[0]["results"])):
            assert (
                results[0]["results"][i]["dominant_emotion"]
                == results[1]["results"][i]["dominant_emotion"]
                and results[0]["results"][i]["dominant_emotion"]
                == results[2]["results"][i]["dominant_emotion"]
            )

            assert (
                results[0]["results"][i]["dominant_gender"]
                == results[1]["results"][i]["dominant_gender"]
                and results[0]["results"][i]["dominant_gender"]
                == results[2]["results"][i]["dominant_gender"]
            )

            assert (
                results[0]["results"][i]["dominant_race"]
                == results[1]["results"][i]["dominant_race"]
                and results[0]["results"][i]["dominant_race"]
                == results[2]["results"][i]["dominant_race"]
            )

        logger.info("✅ different inputs test is done")

    def test_invalid_verify(self):
        data = {
            "img1": "dataset/invalid_1.jpg",
            "img2": "dataset/invalid_2.jpg",
        }
        response = self.app.post("/verify", json=data)
        assert response.status_code == 400
        logger.info("✅ invalid verification request api test is done")

    def test_invalid_represent(self):
        data = {
            "img": "dataset/invalid_1.jpg",
        }
        response = self.app.post("/represent", json=data)
        assert response.status_code == 400
        logger.info("✅ invalid represent request api test is done")

    def test_invalid_analyze(self):
        data = {
            "img": "dataset/invalid.jpg",
        }
        response = self.app.post("/analyze", json=data)
        assert response.status_code == 400

    def test_analyze_for_multipart_form_data(self):
        if is_form_data_file_testable() is False:
            return

        with open("/tmp/img1.jpg", "rb") as img_file:
            response = self.app.post(
                "/analyze",
                content_type="multipart/form-data",
                data={
                    "img": (img_file, "test_image.jpg"),
                    "actions": '["age", "gender"]',
                    "detector_backend": "mtcnn",
                },
            )
            assert response.status_code == 200
            result = response.json
            assert isinstance(result, dict)
            assert result.get("age") is not True
            assert result.get("dominant_gender") is not True
            logger.info("✅ analyze api for multipart form data test is done")

    def test_verify_for_multipart_form_data(self):
        if is_form_data_file_testable() is False:
            return

        with open("/tmp/img1.jpg", "rb") as img1_file:
            with open("/tmp/img2.jpg", "rb") as img2_file:
                response = self.app.post(
                    "/verify",
                    content_type="multipart/form-data",
                    data={
                        "img1": (img1_file, "first_image.jpg"),
                        "img2": (img2_file, "second_image.jpg"),
                        "model_name": "Facenet",
                        "detector_backend": "mtcnn",
                        "distance_metric": "euclidean",
                    },
                )
                assert response.status_code == 200
                result = response.json
                assert isinstance(result, dict)
                assert result.get("verified") is not None
                assert result.get("model") == "Facenet"
                assert result.get("similarity_metric") is not None
                assert result.get("detector_backend") == "mtcnn"
                assert result.get("threshold") is not None
                assert result.get("facial_areas") is not None

                logger.info("✅ verify api for multipart form data test is done")

    def test_represent_for_multipart_form_data(self):
        if is_form_data_file_testable() is False:
            return

        with open("/tmp/img1.jpg", "rb") as img_file:
            response = self.app.post(
                "/represent",
                content_type="multipart/form-data",
                data={
                    "img": (img_file, "first_image.jpg"),
                    "model_name": "Facenet",
                    "detector_backend": "mtcnn",
                },
            )
            assert response.status_code == 200
            result = response.json
            assert isinstance(result, dict)
            logger.info("✅ represent api for multipart form data test is done")

    def test_represent_for_multipart_form_data_and_filepath(self):
        if is_form_data_file_testable() is False:
            return

        response = self.app.post(
            "/represent",
            content_type="multipart/form-data",
            data={
                "img": "/tmp/img1.jpg",
                "model_name": "Facenet",
                "detector_backend": "mtcnn",
            },
        )
        assert response.status_code == 200
        result = response.json
        assert isinstance(result, dict)
        logger.info("✅ represent api for multipart form data and file path test is done")

    def test_extract_image_from_form_data(self):
        if is_form_data_file_testable() is False:
            return

        img_key = "img1"
        img_itself = np.zeros((100, 100, 3), dtype=np.uint8)
        # Establish a temporary request context using the Flask app
        with DUMMY_APP.test_request_context("/dummy_endpoint"):
            # Mock the file part
            with patch("deepface.api.src.modules.core.routes.request") as mock_request:
                mock_file = MagicMock()
                mock_file.filename = "image.jpg"
                mock_request.files = {img_key: mock_file}

                # Mock the image loading function
                with patch(
                    "deepface.commons.image_utils.load_image_from_file_storage",
                    return_value=img_itself,
                ):
                    result = routes.extract_image_from_request(img_key)

                    assert isinstance(result, np.ndarray)
                    assert np.array_equal(result, img_itself)

        logger.info("✅ test extract_image_from_request for real image from form data done")

    def test_extract_image_string_from_json_data(self):
        if is_form_data_file_testable() is False:
            return

        img_key = "img1"
        img_data = "image_url_or_path_or_base64"

        with DUMMY_APP.test_request_context("/dummy_endpoint"):
            with patch("deepface.api.src.modules.core.routes.request") as mock_request:
                # Mock JSON data
                mock_request.files = None
                mock_request.is_json = True
                mock_request.get_json = MagicMock(return_value={img_key: img_data})

                result = routes.extract_image_from_request(img_key)

                assert isinstance(result, str)
                assert result == img_data

        logger.info("✅ test extract_image_from_request for image string from json done")

    def test_extract_image_string_from_form_data(self):
        if is_form_data_file_testable() is False:
            return

        img_key = "img1"
        img_data = "image_url_or_path_or_base64"

        with DUMMY_APP.test_request_context("/dummy_endpoint"):
            with patch("deepface.api.src.modules.core.routes.request") as mock_request:
                # Mock form data
                mock_request.files = None

                mock_request.is_json = False
                mock_request.get_json = MagicMock(return_value=None)

                mock_request.form = MagicMock()
                mock_request.form.to_dict.return_value = {img_key: img_data}

                result = routes.extract_image_from_request(img_key)

                assert isinstance(result, str)
                assert result == img_data

        logger.info("✅ test extract_image_from_request for image string from form done")


def download_test_images(url: str):
    file_name = url.split("/")[-1]
    target_file = f"/tmp/{file_name}"
    if os.path.exists(target_file) is True:
        return

    gdown.download(url, target_file, quiet=False)


def is_form_data_file_testable() -> bool:
    """
    Sending a file from form data fails in unit test with
        415 unsupported media type error for flask 3.X
        but it is working for flask 2.0.2
    Returns:
        is_form_data_file_testable (bool)
    """
    flask_version = version.parse(flask.__version__)
    werkzeus_version = version.parse(werkzeug.__version__)
    threshold_version = version.parse("2.0.2")
    is_testable = flask_version <= threshold_version and werkzeus_version <= threshold_version
    if is_testable is False:
        logger.warn(
            "sending file in form data is not testable because of flask, werkzeus versions."
            f"Expected <= {threshold_version}, but {flask_version=} and {werkzeus_version}."
        )
    return is_testable
