"""Tests unitaires pour MinIOClient."""

import pytest
from unittest.mock import MagicMock, patch
from pydantic import BaseModel

from cezam_lib.cezam_shared import MinIOClient, MinIOError


class SampleModel(BaseModel):
    """Modèle de test."""

    name: str
    value: int


class TestMinIOClientInit:
    """Tests d'initialisation du client."""

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_init_creates_client(self, mock_minio: MagicMock) -> None:
        """Vérifie que le constructeur crée un client Minio."""
        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
            secure=False,
        )
        mock_minio.assert_called_once_with(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            secure=False,
        )
        assert client._bucket == "test-bucket"

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_init_with_secure(self, mock_minio: MagicMock) -> None:
        """Vérifie que secure=True est passé au client."""
        MinIOClient(
            endpoint="minio.example.com",
            access_key="access",
            secret_key="secret",
            bucket="bucket",
            secure=True,
        )
        mock_minio.assert_called_once_with(
            endpoint="minio.example.com",
            access_key="access",
            secret_key="secret",
            secure=True,
        )

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_init_failure_raises_minio_error(self, mock_minio: MagicMock) -> None:
        """Vérifie qu'une erreur de connexion lève MinIOError."""
        mock_minio.side_effect = Exception("Connection refused")
        with pytest.raises(MinIOError, match="Échec de connexion"):
            MinIOClient(
                endpoint="localhost:9000",
                access_key="access",
                secret_key="secret",
                bucket="bucket",
            )


class TestMinIOClientPutJson:
    """Tests pour put_json."""

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_put_json_dict(self, mock_minio: MagicMock) -> None:
        """Vérifie l'écriture d'un dict."""
        mock_client = MagicMock()
        mock_minio.return_value = mock_client

        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
        )
        client.put_json("path/to/file.json", {"key": "value"})

        mock_client.put_object.assert_called_once()
        call_args = mock_client.put_object.call_args
        assert call_args.kwargs["bucket_name"] == "test-bucket"
        assert call_args.kwargs["object_name"] == "path/to/file.json"
        assert call_args.kwargs["content_type"] == "application/json"

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_put_json_pydantic_model(self, mock_minio: MagicMock) -> None:
        """Vérifie l'écriture d'un modèle Pydantic."""
        mock_client = MagicMock()
        mock_minio.return_value = mock_client

        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
        )
        model = SampleModel(name="test", value=42)
        client.put_json("model.json", model)

        mock_client.put_object.assert_called_once()

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_put_json_s3_error_raises_minio_error(self, mock_minio: MagicMock) -> None:
        """Vérifie qu'une erreur S3 lève MinIOError."""
        from minio.error import S3Error

        mock_client = MagicMock()
        mock_minio.return_value = mock_client
        mock_client.put_object.side_effect = S3Error(
            code="AccessDenied",
            message="Access Denied",
            resource="/bucket/key",
            request_id="123",
            host_id="host",
            response=MagicMock(),
        )

        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
        )
        with pytest.raises(MinIOError, match="Échec d'écriture"):
            client.put_json("file.json", {"key": "value"})


class TestMinIOClientGetJson:
    """Tests pour get_json."""

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_get_json_returns_dict(self, mock_minio: MagicMock) -> None:
        """Vérifie la lecture d'un JSON."""
        mock_client = MagicMock()
        mock_minio.return_value = mock_client

        mock_response = MagicMock()
        mock_response.read.return_value = b'{"key": "value"}'
        mock_client.get_object.return_value = mock_response

        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
        )
        result = client.get_json("file.json")

        assert result == {"key": "value"}
        mock_response.close.assert_called_once()
        mock_response.release_conn.assert_called_once()

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_get_json_not_found_raises_minio_error(self, mock_minio: MagicMock) -> None:
        """Vérifie qu'un fichier inexistant lève MinIOError."""
        from minio.error import S3Error

        mock_client = MagicMock()
        mock_minio.return_value = mock_client
        mock_client.get_object.side_effect = S3Error(
            code="NoSuchKey",
            message="Not Found",
            resource="/bucket/key",
            request_id="123",
            host_id="host",
            response=MagicMock(),
        )

        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
        )
        with pytest.raises(MinIOError, match="Objet inexistant"):
            client.get_json("nonexistent.json")

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_get_json_invalid_json_raises_minio_error(
        self, mock_minio: MagicMock
    ) -> None:
        """Vérifie qu'un JSON invalide lève MinIOError."""
        mock_client = MagicMock()
        mock_minio.return_value = mock_client

        mock_response = MagicMock()
        mock_response.read.return_value = b"not valid json"
        mock_client.get_object.return_value = mock_response

        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
        )
        with pytest.raises(MinIOError, match="JSON invalide"):
            client.get_json("invalid.json")


class TestMinIOClientExists:
    """Tests pour exists."""

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_exists_returns_true_when_object_exists(
        self, mock_minio: MagicMock
    ) -> None:
        """Vérifie que exists retourne True si l'objet existe."""
        mock_client = MagicMock()
        mock_minio.return_value = mock_client
        mock_client.stat_object.return_value = MagicMock()

        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
        )
        assert client.exists("existing.json") is True

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_exists_returns_false_when_object_not_found(
        self, mock_minio: MagicMock
    ) -> None:
        """Vérifie que exists retourne False si l'objet n'existe pas."""
        from minio.error import S3Error

        mock_client = MagicMock()
        mock_minio.return_value = mock_client
        mock_client.stat_object.side_effect = S3Error(
            code="NoSuchKey",
            message="Not Found",
            resource="/bucket/key",
            request_id="123",
            host_id="host",
            response=MagicMock(),
        )

        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
        )
        assert client.exists("nonexistent.json") is False

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_exists_raises_on_other_s3_error(self, mock_minio: MagicMock) -> None:
        """Vérifie qu'une autre erreur S3 lève MinIOError."""
        from minio.error import S3Error

        mock_client = MagicMock()
        mock_minio.return_value = mock_client
        mock_client.stat_object.side_effect = S3Error(
            code="AccessDenied",
            message="Access Denied",
            resource="/bucket/key",
            request_id="123",
            host_id="host",
            response=MagicMock(),
        )

        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
        )
        with pytest.raises(MinIOError, match="Échec de vérification"):
            client.exists("file.json")


class TestMinIOClientListPrefix:
    """Tests pour list_prefix."""

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_list_prefix_returns_object_names(self, mock_minio: MagicMock) -> None:
        """Vérifie que list_prefix retourne les noms d'objets."""
        mock_client = MagicMock()
        mock_minio.return_value = mock_client

        obj1 = MagicMock()
        obj1.object_name = "prefix/file1.json"
        obj2 = MagicMock()
        obj2.object_name = "prefix/file2.json"
        mock_client.list_objects.return_value = [obj1, obj2]

        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
        )
        result = client.list_prefix("prefix/")

        assert result == ["prefix/file1.json", "prefix/file2.json"]
        mock_client.list_objects.assert_called_once_with(
            bucket_name="test-bucket",
            prefix="prefix/",
            recursive=True,
        )

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_list_prefix_empty_returns_empty_list(self, mock_minio: MagicMock) -> None:
        """Vérifie que list_prefix retourne une liste vide si aucun objet."""
        mock_client = MagicMock()
        mock_minio.return_value = mock_client
        mock_client.list_objects.return_value = []

        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
        )
        result = client.list_prefix("nonexistent/")

        assert result == []

    @patch("cezam_lib.cezam_shared.minio_client.Minio")
    def test_list_prefix_s3_error_raises_minio_error(
        self, mock_minio: MagicMock
    ) -> None:
        """Vérifie qu'une erreur S3 lève MinIOError."""
        from minio.error import S3Error

        mock_client = MagicMock()
        mock_minio.return_value = mock_client
        mock_client.list_objects.side_effect = S3Error(
            code="AccessDenied",
            message="Access Denied",
            resource="/bucket",
            request_id="123",
            host_id="host",
            response=MagicMock(),
        )

        client = MinIOClient(
            endpoint="localhost:9000",
            access_key="access",
            secret_key="secret",
            bucket="test-bucket",
        )
        with pytest.raises(MinIOError, match="Échec de listage"):
            client.list_prefix("prefix/")
