#!/usr/bin/env python
# -*-coding:utf-8 -*-

"""████
 ██    ██    Datature
   ██  ██    Powering Breakthrough AI
     ██

@File    :   test_training_projects.py
@Author  :   Wei Loon Cheng
@Contact :   developers@datature.io
@License :   Apache License 2.0
@Desc    :   Tests for training projects API operations.
"""

from unittest.mock import Mock

import pytest

from vi.api.pagination import PaginatedResponse
from vi.api.resources.training_projects import responses
from vi.api.resources.training_projects.training_projects import TrainingProject
from vi.api.responses import DeletedResource, Pagination
from vi.client.errors import ViInvalidParameterError

VALID_TRAINING_PROJECT_ID = "project_123"


@pytest.mark.unit
@pytest.mark.training_project
class TestTrainingProjectResource:
    """Test suite for TrainingProject resource basic operations."""

    @pytest.fixture
    def training_project(self, mock_auth, mock_requester):
        """Create a TrainingProject instance for testing.

        Args:
            mock_auth: Mock authentication object
            mock_requester: Mock HTTP requester

        Returns:
            TrainingProject: Configured training project resource instance

        """
        return TrainingProject(mock_auth, mock_requester)

    def test_init(self, training_project):
        """Test training project initialization.

        Verifies that TrainingProject instance is properly initialized with
        required attributes like link parser.

        """
        assert training_project._link_parser is not None

    def test_list_training_projects(self, training_project, mock_requester):
        """Test listing training projects.

        Verifies that the list method correctly retrieves and paginates
        training projects for the organization.

        """
        mock_project = Mock(spec=responses.TrainingProject)
        mock_project.training_project_id = VALID_TRAINING_PROJECT_ID

        mock_response = Pagination(items=[mock_project], next_page=None, prev_page=None)
        mock_requester.get.return_value = mock_response

        result = training_project.list()

        assert isinstance(result, PaginatedResponse)
        assert len(result.items) == 1
        assert result.items[0].training_project_id == VALID_TRAINING_PROJECT_ID

    def test_list_training_projects_with_pagination(
        self, training_project, mock_requester
    ):
        """Test listing training projects with pagination.

        Verifies that pagination is properly handled when listing projects,
        including next_page tokens for fetching additional pages.

        """
        mock_project1 = Mock(spec=responses.TrainingProject)
        mock_project1.training_project_id = "project1"

        mock_project2 = Mock(spec=responses.TrainingProject)
        mock_project2.training_project_id = "project2"

        mock_response = Pagination(
            items=[mock_project1, mock_project2],
            next_page="token",
            prev_page=None,
        )
        mock_requester.get.return_value = mock_response

        result = training_project.list()

        assert isinstance(result, PaginatedResponse)
        assert len(result.items) == 2
        assert result.next_page == "token"

    def test_get_training_project(self, training_project, mock_requester):
        """Test retrieving a single training project by ID.

        Verifies that the get method correctly retrieves a training project
        using its ID with all expected attributes.

        """
        mock_project = Mock(spec=responses.TrainingProject)
        mock_project.training_project_id = VALID_TRAINING_PROJECT_ID

        mock_requester.get.return_value = mock_project

        result = training_project.get(VALID_TRAINING_PROJECT_ID)

        assert isinstance(result, responses.TrainingProject)
        assert result.training_project_id == VALID_TRAINING_PROJECT_ID

    def test_get_training_project_invalid_id(self, training_project):
        """Test getting training project with invalid ID.

        Verifies that attempting to get a training project with an empty
        ID raises ViInvalidParameterError.

        """
        with pytest.raises(ViInvalidParameterError):
            training_project.get("")

    def test_create_training_project(self, training_project, mock_requester):
        """Test creating a training project.

        Verifies that the create method correctly creates a training project
        and returns the created resource.

        """
        mock_project = Mock(spec=responses.TrainingProject)
        mock_project.training_project_id = VALID_TRAINING_PROJECT_ID

        mock_requester.put.return_value = mock_project

        result = training_project.create(
            spec={"name": "Test Project", "type": "vqa"},
            training_project_id=VALID_TRAINING_PROJECT_ID,
        )

        assert isinstance(result, responses.TrainingProject)
        assert result.training_project_id == VALID_TRAINING_PROJECT_ID

    def test_create_training_project_auto_id(self, training_project, mock_requester):
        """Test creating a training project with auto-generated ID.

        Verifies that the create method generates an ID when none is provided.

        """
        mock_project = Mock(spec=responses.TrainingProject)
        mock_project.training_project_id = "auto-generated-id"

        mock_requester.put.return_value = mock_project

        result = training_project.create(spec={"name": "Test Project", "type": "vqa"})

        assert isinstance(result, responses.TrainingProject)
        mock_requester.put.assert_called_once()

    def test_update_training_project(self, training_project, mock_requester):
        """Test updating a training project.

        Verifies that the update method correctly patches a training project
        and returns the updated resource.

        """
        mock_project = Mock(spec=responses.TrainingProject)
        mock_project.training_project_id = VALID_TRAINING_PROJECT_ID

        mock_requester.patch.return_value = mock_project

        result = training_project.update(
            training_project_id=VALID_TRAINING_PROJECT_ID,
            spec={"name": "Updated Name"},
        )

        assert isinstance(result, responses.TrainingProject)
        assert result.training_project_id == VALID_TRAINING_PROJECT_ID

    def test_update_training_project_invalid_id(self, training_project):
        """Test updating training project with invalid ID.

        Verifies that attempting to update a training project with an empty
        ID raises ViInvalidParameterError.

        """
        with pytest.raises(ViInvalidParameterError):
            training_project.update("", spec={"name": "test"})

    def test_delete_training_project(self, training_project, mock_requester):
        """Test deleting a training project.

        Verifies that the delete method correctly removes a training project
        and returns a DeletedResource confirmation.

        """
        mock_requester.delete.return_value = {"data": ""}

        result = training_project.delete(VALID_TRAINING_PROJECT_ID)

        assert isinstance(result, DeletedResource)
        assert result.id == VALID_TRAINING_PROJECT_ID
        assert result.deleted is True

    def test_delete_training_project_invalid_id(self, training_project):
        """Test deleting training project with invalid ID.

        Verifies that attempting to delete a training project with an empty
        ID raises ViInvalidParameterError.

        """
        with pytest.raises(ViInvalidParameterError):
            training_project.delete("")

    def test_get_aggregation_insights(self, training_project, mock_requester):
        """Test getting aggregation insights.

        Verifies that the get_aggregation_insights method correctly retrieves
        aggregation insights for a training project.

        """
        mock_result = Mock(spec=responses.AggregationInsightsResult)
        mock_result.payload = None
        mock_result.pending_operation = None

        mock_requester.get.return_value = mock_result

        result = training_project.get_aggregation_insights(VALID_TRAINING_PROJECT_ID)

        assert isinstance(result, responses.AggregationInsightsResult)

    def test_get_aggregation_insights_invalid_id(self, training_project):
        """Test getting aggregation insights with invalid ID.

        Verifies that attempting to get aggregation insights with an empty
        ID raises ViInvalidParameterError.

        """
        with pytest.raises(ViInvalidParameterError):
            training_project.get_aggregation_insights("")

    def test_generate_aggregation_insights(self, training_project, mock_requester):
        """Test generating aggregation insights.

        Verifies that the generate_aggregation_insights method correctly
        triggers insight generation.

        """
        mock_requester.post.return_value = None

        training_project.generate_aggregation_insights(VALID_TRAINING_PROJECT_ID)

        mock_requester.post.assert_called_once()

    def test_dry_run_create(self, training_project, mock_requester):
        """Test dry run create validation.

        Verifies that the dry_run_create method correctly validates
        project creation without actually creating it.

        """
        mock_result = Mock(spec=responses.DryRunResponse)
        mock_result.errors = []

        mock_requester.post.return_value = mock_result

        result = training_project.dry_run_create(
            training_project_id="my-project",
            name="My Project",
        )

        assert isinstance(result, responses.DryRunResponse)
        assert len(result.errors) == 0

    def test_dry_run_create_with_conflicts(self, training_project, mock_requester):
        """Test dry run create with conflicts.

        Verifies that the dry_run_create method correctly returns
        validation errors when conflicts exist.

        """
        mock_error = Mock(spec=responses.DryRunError)
        mock_error.kind = "IdConflict"

        mock_result = Mock(spec=responses.DryRunResponse)
        mock_result.errors = [mock_error]

        mock_requester.post.return_value = mock_result

        result = training_project.dry_run_create(
            training_project_id="existing-project",
            name="Existing Project",
        )

        assert isinstance(result, responses.DryRunResponse)
        assert len(result.errors) == 1
        assert result.errors[0].kind == "IdConflict"


@pytest.mark.unit
@pytest.mark.training_project
class TestTrainingProjectEdgeCases:
    """Test suite for training project edge cases and error handling."""

    @pytest.fixture
    def training_project(self, mock_auth, mock_requester):
        """Create a TrainingProject instance for testing.

        Args:
            mock_auth: Mock authentication object
            mock_requester: Mock HTTP requester

        Returns:
            TrainingProject: Configured training project resource instance

        """
        return TrainingProject(mock_auth, mock_requester)

    def test_list_invalid_response(self, training_project, mock_requester):
        """Test list with invalid response format.

        Verifies that the list method raises ValueError when the API
        returns an invalid response format.

        """
        mock_requester.get.return_value = {"invalid": "response"}

        with pytest.raises(ValueError):
            training_project.list()

    def test_get_invalid_response(self, training_project, mock_requester):
        """Test get with invalid response format.

        Verifies that the get method raises ValueError when the API
        returns an invalid response format.

        """
        mock_requester.get.return_value = {"invalid": "response"}

        with pytest.raises(ValueError):
            training_project.get(VALID_TRAINING_PROJECT_ID)

    def test_delete_invalid_response(self, training_project, mock_requester):
        """Test delete with invalid response format.

        Verifies that the delete method raises ValueError when the API
        returns an unexpected response format.

        """
        mock_requester.delete.return_value = {"data": "unexpected"}

        with pytest.raises(ValueError):
            training_project.delete(VALID_TRAINING_PROJECT_ID)

    def test_list_empty_training_projects(self, training_project, mock_requester):
        """Test listing when no training projects exist.

        Verifies that the list method correctly handles an empty result
        set and returns an empty PaginatedResponse.

        """
        mock_response = Pagination(items=[], next_page=None, prev_page=None)
        mock_requester.get.return_value = mock_response

        result = training_project.list()

        assert isinstance(result, PaginatedResponse)
        assert len(result.items) == 0

    def test_create_invalid_response(self, training_project, mock_requester):
        """Test create with invalid response format.

        Verifies that the create method raises ValueError when the API
        returns an invalid response format.

        """
        mock_requester.put.return_value = {"invalid": "response"}

        with pytest.raises(ValueError):
            training_project.create(
                spec={"name": "Test", "type": "vqa"},
                training_project_id=VALID_TRAINING_PROJECT_ID,
            )

    def test_update_invalid_response(self, training_project, mock_requester):
        """Test update with invalid response format.

        Verifies that the update method raises ValueError when the API
        returns an invalid response format.

        """
        mock_requester.patch.return_value = {"invalid": "response"}

        with pytest.raises(ValueError):
            training_project.update(
                training_project_id=VALID_TRAINING_PROJECT_ID,
                spec={"name": "test"},
            )
