import math
from unittest import TestCase
import unittest
from pygeai.organization.clients import OrganizationClient
from pygeai.core.common.exceptions import APIResponseError 


class TestOrgAccessControlProjectMembersIntegration(TestCase):
    __project_attrs = [
        'members'
    ]    

    __member_attrs = [
        'accessType', 'id', 'name', 'roles'
    ]

    __role_attrs = [
        'externalId', 'id', 'name', 'origin', 'type'
    ]

    def setUp(self):
        """
        Set up the test environment.
        """
        self.organization_client = OrganizationClient(alias="org")
        self.project_id = "be4889df-cacc-4e6f-b3bb-153c4ac0d168"


    def __get_project_members(self, project_id=None, **kwargs):
        """
        Helper method to call get_project_members with default or custom parameters.
        """
        return self.organization_client.get_project_members(
            project_id=project_id if project_id is not None else self.project_id,
            **kwargs
        )


    def test_get_project_members_valid_project_id(self):
        """Test successful retrieval of project members with valid project ID."""
        response = self.__get_project_members()

        self.assertIn("count", response, "Response should have a 'count' key")        
        self.assertIn("pages", response, "Response should have a 'pages' key")
        
        for project in response["projects"]:            
            for project_attr in self.__project_attrs:
                self.assertIn(project_attr, project, f"Response should have a '{project_attr}' key")
                
                for member in project["members"]: 
                    for member_attr in self.__member_attrs:
                        self.assertIn(member_attr, member, f"Response should have a '{member_attr}' key")
                    
                    for role in member["roles"]:
                        for role_attr in self.__role_attrs:
                            self.assertIn(role_attr, role, f"Response should have a '{role_attr}' key")


    def test_get_project_members_with_pagination(self):
        """Test project members retrieval with pagination parameters."""
        test_params = [
            {"start_page": 1, "page_size": 1},
            {"start_page": 1, "page_size": 2},
            {"start_page": 1, "page_size": 3},
        ]
        
        for params in test_params:
            with self.subTest(params=params):
                response = self.__get_project_members(**params)

                expected_pages = math.ceil(response["count"] / params["page_size"]) 
                self.assertEqual(
                    response["pages"], expected_pages,
                    f"Expected {expected_pages} pages for params {params}, got {response['pages']}"
                )


    def test_get_project_members_with_ordering(self):
        """Test project members retrieval with different ordering parameters."""
        test_params = [
            {"order_key": "name", "order_direction": "asc"},
            {"order_key": "name", "order_direction": "desc"}
        ]
        
        for params in test_params:
            with self.subTest(params=params):
                response = self.__get_project_members(**params)

                members = response["projects"][0]["members"]
                isOrdered = False
                if params.get("order_direction") == "asc":
                    isOrdered = all(
                        members[i - 1]["name"] <= members[i]["name"]
                        for i in range(1, len(members))
                    )
                else:
                    isOrdered = all(
                        members[i - 1]["name"] >= members[i]["name"]
                        for i in range(1, len(members))
                    )

                self.assertTrue(
                    isOrdered, 
                    f"Members should be sorted in {'ascending' if params['order_direction'] == 'asc' else 'descending'} order by name"
                )


    def test_get_project_members_with_role_types_filtering(self):
        """Test project members retrieval with role types filtering."""
        test_params = [
            {"role_types": "backend"},
            {"role_types": "frontend"}
        ]
        
        for params in test_params:
            with self.subTest(params=params):
                response = self.__get_project_members(**params)
                
                if response["count"] > 0:
                    for member in response["projects"][0]["members"]:
                        for role in member["roles"]:
                            if params["role_types"] == "backend":
                                self.assertEqual(
                                    role.get("type"), 
                                    "Backend",
                                    f"Role type should be Backend for params {params}"
                                )
                            else:
                                self.assertEqual(
                                    role.get("type"), 
                                    "Frontend",
                                    f"Role type should be Frontend for params {params}"
                                )

    
    def test_get_project_members_invalid_project_id(self):
        """Test error when using invalid project ID."""
        with self.assertRaises(APIResponseError) as exception:
            self.__get_project_members(project_id="invalid-project-id")

        self.assertIn(
            "Organization or Project mismatch: verify the provided authorization",
            str(exception.exception)
        )


    def test_get_project_members_empty_project_id(self):
        """Test error when project ID is empty."""
        with self.assertRaises(APIResponseError) as exception:
            self.__get_project_members(project_id="")

        self.assertIn(
            "Organization or Project mismatch: verify the provided authorization",
            str(exception.exception)
        )


    def test_get_project_members_invalid_role_types(self):
        """Test behavior with invalid role types."""
        invalid_role_types = ["invalid_role", "nonexistent", "bad_type"]
        
        for role_type in invalid_role_types:
            with self.subTest(role_type=role_type):
                # Some implementations might filter out invalid types, others might error
                with self.assertRaises(APIResponseError) as exception:
                    self.__get_project_members(role_types=role_type)

                self.assertIn(
                   "Field roleTypes is out of range",
                    str(exception.exception),
                    f"Expected error about invalid role type for {role_type}"
                )


    def test_get_project_members_unauthorized_access(self):
        """Test error when user doesn't have access to the project."""
        # Create a client with invalid credentials
        unauthorized_client = OrganizationClient(api_key="invalid", base_url="https://api.beta.saia.ai")
        
        with self.assertRaises(APIResponseError) as exception:
            unauthorized_client.get_project_members(project_id=self.project_id)

        self.assertIn(
            "unauthorized",
            str(exception.exception).lower(),
            "Expected authorization-related error message"
        )


    def test_get_project_members_no_access_to_project(self):
        """Test error when user has valid credentials but no access to specific project."""
        # project Id that exists but it's not accessible by the api_key
        restricted_project_id = "6f5c69f1-232d-47d6-84f8-8bec87946bbc"
        
        with self.assertRaises(APIResponseError) as exception:
            self.__get_project_members(project_id=restricted_project_id)

        self.assertIn(
            "Organization or Project mismatch: verify the provided authorization",
            str(exception.exception),
            "Expected access-related error message"
        )


    def test_get_project_members_empty_response_handling(self):
        """Test handling when project has no members."""
        # This might not be possible in real scenarios, but test graceful handling
        response = self.__get_project_members(page_size=1)
        
        self.assertIn("count", response)
        self.assertIn("pages", response)
        self.assertIn("projects", response)
        
        if response["count"] == 0:
            # If no members, should still have proper structure
            self.assertEqual(response["projects"][0]["members"], [])


    def test_get_project_members_large_page_size(self):
        """Test behavior with very large page sizes."""
        response = self.__get_project_members(page_size=1000)
        
        self.assertIn("count", response)
        # Should handle large page sizes gracefully
        self.assertLessEqual(len(response["projects"][0]["members"]), 1000)