import requests
from typing import Dict, Any, Optional, List, Union
from requests.exceptions import RequestException
from adxp_sdk.auth import BaseCredentials
from adxp_sdk.base import BaseClient
from .utils import is_valid_uuid


class AuthorizationClient(BaseClient):
    """
    Authorization Client for managing projects, users, and groups.

    This client provides functionality for managing authorization and access control including:
    - Project management (CRUD operations)
    - User management (list, create)
    - Group management (list)

    How to use:
        >>> from adxp_sdk.auth import TokenCredentials
        >>> from adxp_sdk.authorization import AuthorizationClient
        >>> 
        >>> # Initialize client with credentials
        >>> credentials = TokenCredentials(
        ...     base_url="https://aip.sktai.io",
        ...     username="user",
        ...     password="pw",
        ...     client_id="cid"
        ... )
        >>> client = AuthorizationClient(credentials)
        >>> 
        >>> # List all projects
        >>> projects = client.list_projects(page=1, size=10)
        >>> 
        >>> # Create a new project
        >>> new_project = client.create_project(
        ...     name="my-project",
        ...     cpu_quota=10,
        ...     mem_quota=20,
        ...     gpu_quota=1
        ... )
        >>> 
        >>> # List all users
        >>> users = client.list_users(page=1, size=10)
        >>> 
        >>> # List all groups
        >>> groups = client.list_groups(page=1, size=10)
    """

    def __init__(
        self,
        credentials: BaseCredentials,
    ):
        """
        Initialize the AuthorizationClient.

        Args:
            credentials: Authentication credentials (BaseCredentials)
        """
        super().__init__(credentials)

    # ====================================================================
    # Project Management (CRUD)
    # ====================================================================
    # - List Projects
    # - Create Project
    # - Update Project
    # - Delete Project
    # ====================================================================

    def list_projects(
        self,
        page: int = 1,
        size: int = 10,
        sort: Optional[str] = None,
        filter: Optional[str] = None,
        search: Optional[str] = None,
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """Fetch project list via GET /api/v1/projects"""
        try:
            params: Dict[str, Any] = {"page": page, "size": size}
            if sort:
                params["sort"] = sort
            if filter:
                params["filter"] = filter
            if search:
                params["search"] = search
            return self._make_request(
                method="GET",
                endpoint=f"api/v1/projects",
                params=params
            )
        except RequestException as e:
            raise RequestException(f"Failed to list projects: {str(e)}")

    def create_project(self, name: str, cpu_quota: int, mem_quota: int, gpu_quota: int = 0) -> Union[Dict[str, Any], List[Any], Any]:
        """Create a new project via POST /api/v1/projects"""
        try:
            payload = {
                "project": {"name": name},
                "namespace": {
                    "cpu_quota": cpu_quota,
                    "mem_quota": mem_quota,
                    "gpu_quota": gpu_quota,
                },
            }
            return self._make_request(
                method="POST",
                endpoint=f"api/v1/projects",
                json_data=payload
            )
        except RequestException as e:
            raise RequestException(f"Failed to create project: {str(e)}")

    def update_project(self, project_id: str, name: str, cpu_quota: int, mem_quota: int, gpu_quota: int = 0) -> Union[Dict[str, Any], List[Any], Any]:
        """Update an existing project via PUT /api/v1/projects/{project_id}"""
        try:
            if not is_valid_uuid(project_id):
                raise ValueError(f"Invalid project_id format: {project_id}")

            payload = {
                "project": {"name": name},
                "namespace": {
                    "cpu_quota": cpu_quota,
                    "mem_quota": mem_quota,
                    "gpu_quota": gpu_quota,
                },
            }
            return self._make_request(
                method="PUT",
                endpoint=f"api/v1/projects/{project_id}",
                json_data=payload
            )
        except RequestException as e:
            raise RequestException(f"Failed to update project {project_id}: {str(e)}")

    def delete_project(self, project_id: str) -> Dict[str, Any]:
        """Delete a project via DELETE /api/v1/projects/{project_id}"""
        try:
            if not is_valid_uuid(project_id):
                raise ValueError(f"Invalid project_id format: {project_id}")

            result = self._make_request(
                method="DELETE",
                endpoint=f"api/v1/projects/{project_id}"
            )
            # Handle empty response
            if not isinstance(result, dict):
                return {"status": "deleted", "project_id": project_id}
            if not result or result.get("status_code") == 204:
                return {"status": "deleted", "project_id": project_id}
            return result
        except RequestException as e:
            raise RequestException(f"Failed to delete project {project_id}: {str(e)}")


    # ====================================================================
    # Project Role Management
    # ====================================================================
    # - List Roles in a Project
    # - Create Role
    # - Update Role
    # - Delete Role
    # ====================================================================

    def list_project_roles(
        self,
        client_id: str,
        page: int = 1,
        size: int = 10,
        sort: Optional[str] = None,
        filter: Optional[str] = None,
        search: Optional[str] = None,
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """Fetch role list for a project via GET /api/v1/projects/{client_id}/roles"""
        try:
            params: Dict[str, Any] = {"page": page, "size": size}
            if sort:
                params["sort"] = sort
            if filter:
                params["filter"] = filter
            if search:
                params["search"] = search
            return self._make_request(
                method="GET",
                endpoint=f"api/v1/projects/{client_id}/roles",
                params=params
            )
        except RequestException as e:
            raise RequestException(f"Failed to list roles for project {client_id}: {str(e)}")

    def create_project_role(self, client_id: str, name: str, description: str = "") -> Union[Dict[str, Any], List[Any], Any]:
        """Create a new role in a project via POST /api/v1/projects/{client_id}/roles"""
        try:
            payload = {"name": name, "description": description}
            return self._make_request(
                method="POST",
                endpoint=f"api/v1/projects/{client_id}/roles",
                json_data=payload
            )
        except RequestException as e:
            raise RequestException(f"Failed to create role for project {client_id}: {str(e)}")

    def update_project_role(self, client_id: str, role_name: str, description: str) -> Union[Dict[str, Any], List[Any], Any]:
        """Update an existing role's description via PUT /api/v1/projects/{client_id}/roles/{role_name}"""
        try:
            params = {"description": description}
            resp = self._make_request(
                method="PUT",
                endpoint=f"api/v1/projects/{client_id}/roles/{role_name}",
                params=params
            )
            # Handle 204 No Content
            if not isinstance(resp, dict):
                return {"status": "updated", "role_name": role_name, "description": description}
            if not resp or resp.get("status_code") == 204:
                return {"status": "updated", "role_name": role_name, "description": description}
            return resp
        except RequestException as e:
            raise RequestException(f"Failed to update role {role_name} in project {client_id}: {str(e)}")

    def delete_project_role(self, client_id: str, role_name: str) -> Union[Dict[str, Any], List[Any], Any]:
        """Delete a role from a project via DELETE /api/v1/projects/{client_id}/roles/{role_name}"""
        try:
            resp = self._make_request(
                method="DELETE",
                endpoint=f"api/v1/projects/{client_id}/roles/{role_name}"
            )
            # Handle 204 No Content
            if not isinstance(resp, dict):
                return {"status": "deleted", "role_name": role_name}
            if not resp or resp.get("status_code") == 204:
                return {"status": "deleted", "role_name": role_name}
            return resp
        except RequestException as e:
            raise RequestException(f"Failed to delete role {role_name} in project {client_id}: {str(e)}")


    # ====================================================================
    # User Management (CRUD)
    # ====================================================================
    # - List Users
    # - Create User
    # - Update User
    # - Delete User
    # ====================================================================

    def list_users(
        self,
        page: int = 1,
        size: int = 10,
        sort: Optional[str] = None,
        filter: Optional[str] = None,
        search: Optional[str] = None,
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """Fetch user list via GET /api/v1/users"""
        try:
            params: Dict[str, Any] = {"page": page, "size": size}
            if sort:
                params["sort"] = sort
            if filter:
                params["filter"] = filter
            if search:
                params["search"] = search
            return self._make_request(
                method="GET",
                endpoint=f"api/v1/users",
                params=params
            )
        except RequestException as e:
            raise RequestException(f"Failed to list users: {str(e)}")

    def create_user(self, username: str, password: str, email: str, first_name: str, last_name: str) -> Union[Dict[str, Any], List[Any], Any]:
        """Create a new user via POST /api/v1/users/register"""
        try:
            payload = {
                "username": username,
                "password": password,
                "email": email,
                "first_name": first_name,
                "last_name": last_name,
            }
            return self._make_request(
                method="POST",
                endpoint=f"api/v1/users/register",
                json_data=payload
            )
        except RequestException as e:
            raise RequestException(f"Failed to create user: {str(e)}")

    def update_user(self, user_id: str, email: str, first_name: str, last_name: str) -> Union[Dict[str, Any], List[Any], Any]:
        """Update a user via PUT /api/v1/users/{user_id}"""
        try:
            if not is_valid_uuid(user_id):
                raise ValueError(f"Invalid user_id format: {user_id}")

            payload = {
                "email": email,
                "first_name": first_name,
                "last_name": last_name,
            }
            resp = self._make_request(
                method="PUT",
                endpoint=f"api/v1/users/{user_id}",
                json_data=payload
            )
            # Handle 204 No Content
            if not isinstance(resp, dict):
                return {"status": "updated", "user_id": user_id}
            if not resp or resp.get("status_code") == 204:
                return {"status": "updated", "user_id": user_id}
            return resp
        except RequestException as e:
            raise RequestException(f"Failed to update user {user_id}: {str(e)}")

    def delete_user(self, user_id: str) -> Union[Dict[str, Any], List[Any], Any]:
        """Delete a user via DELETE /api/v1/users/{user_id}"""
        try:
            if not is_valid_uuid(user_id):
                raise ValueError(f"Invalid user_id format: {user_id}")

            resp = self._make_request(
                method="DELETE",
                endpoint=f"api/v1/users/{user_id}"
            )
            # Handle 204 No Content
            if not isinstance(resp, dict):
                return {"status": "deleted", "user_id": user_id}
            if not resp or resp.get("status_code") == 204:
                return {"status": "deleted", "user_id": user_id}
            return resp
        except RequestException as e:
            raise RequestException(f"Failed to delete user {user_id}: {str(e)}")
        
        
    # ====================================================================
    # User Role Management
    # ====================================================================
    # Includes:
    # - Available Roles 조회
    # - Assign Roles
    # - Assigned Roles 조회
    # - Delete Roles
    # ====================================================================

    def list_user_available_roles(
        self,
        user_id: str,
        page: int = 1,
        size: int = 10,
        sort: Optional[str] = None,
        filter: Optional[str] = None,
        search: Optional[str] = None,
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """Get roles available for assignment to a specific user via GET /api/v1/users/{user_id}/role-available"""
        try:
            params: Dict[str, Any] = {"page": page, "size": size}
            if sort:
                params["sort"] = sort
            if filter:
                params["filter"] = filter
            if search:
                params["search"] = search
            return self._make_request(
                method="GET",
                endpoint=f"api/v1/users/{user_id}/role-available",
                params=params
            )
        except RequestException as e:
            raise RequestException(f"Failed to list available roles for user {user_id}: {str(e)}")


    def assign_roles_to_user(self, user_id: str, roles: list) -> Union[Dict[str, Any], List[Any], Any]:
        """Assign roles to a user via PUT /api/v1/users/{user_id}/role-mappings
        
        Args:
            user_id: The ID of the user to assign roles to
            roles: List of role assignment objects. Each object must have the following format:
                [
                    {
                        "project": {
                            "id": "string",      # Project ID (client_id)
                            "name": "string}"     # Project name
                        },
                        "role": {
                            "id": "string",      # Role ID (optional, can be empty string)
                            "name": "string",    # Role name
                            "description": "string}"  # Role description (optional)
                        }
                    }
                ]
        
        Example:
            roles = [
                {
                    "project": {
                        "id": "project-uuid",
                        "name": "my-project}"
                    },
                    "role": {
                        "id": "role-uuid",
                        "name": "admin",
                        "description": "Administrator role}"
                    }
                }
            ]
        """
        try:
            resp = self._make_request(
                method="PUT",
                endpoint=f"api/v1/users/{user_id}/role-mappings",
                json_data=roles
            )
            # Handle 204 No Content
            if not isinstance(resp, dict):
                return {"status": "assigned", "user_id": user_id, "roles": roles}
            if not resp or resp.get("status_code") == 204:
                return {"status": "assigned", "user_id": user_id, "roles": roles}
            return resp
        except RequestException as e:
            raise RequestException(f"Failed to assign roles to user {user_id}: {str(e)}")


    def list_user_assigned_roles(
        self,
        user_id: str,
        page: int = 1,
        size: int = 10,
        sort: Optional[str] = None,
        filter: Optional[str] = None,
        search: Optional[str] = None,
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """Get roles currently assigned to a specific user via GET /api/v1/users/{user_id}/role-mappings"""
        try:
            params: Dict[str, Any] = {"page": page, "size": size}
            if sort:
                params["sort"] = sort
            if filter:
                params["filter"] = filter
            if search:
                params["search"] = search
            return self._make_request(
                method="GET",
                endpoint=f"api/v1/users/{user_id}/role-mappings",
                params=params
            )
        except RequestException as e:
            raise RequestException(f"Failed to list assigned roles for user {user_id}: {str(e)}")


    def delete_roles_from_user(self, user_id: str, roles: list) -> Union[Dict[str, Any], List[Any], Any]:
        """Delete roles from a user via DELETE /api/v1/users/{user_id}/role-mappings
        
        Args:
            user_id: The ID of the user to remove roles from
            roles: List of role assignment objects. Each object must have the following format:
                [
                    {
                        "project": {
                            "id": "string",      # Project ID (client_id)
                            "name": "string}"     # Project name
                        },
                        "role": {
                            "id": "string",      # Role ID (optional, can be empty string)
                            "name": "string",    # Role name
                            "description": "string}"  # Role description (optional)
                        }
                    }
                ]
        
        Example:
            roles = [
                {
                    "project": {
                        "id": "project-uuid",
                        "name": "my-project}"
                    },
                    "role": {
                        "id": "role-uuid",
                        "name": "admin",
                        "description": "Administrator role}"
                    }
                }
            ]
        """
        try:
            resp = self._make_request(
                method="DELETE",
                endpoint=f"api/v1/users/{user_id}/role-mappings",
                json_data=roles
            )
            # Handle 204 No Content
            if not isinstance(resp, dict):
                return {"status": "deleted", "user_id": user_id, "roles": roles}
            if not resp or resp.get("status_code") == 204:
                return {"status": "deleted", "user_id": user_id, "roles": roles}
            return resp
        except RequestException as e:
            raise RequestException(f"Failed to delete roles from user {user_id}: {str(e)}")
        
        
    # ====================================================================
    # User Group Management
    # ====================================================================
    # - List Available Groups
    # - List Assigned Groups
    # - Assign Group
    # - Delete Group
    # ====================================================================

    def list_user_available_groups(
        self,
        user_id: str,
        page: int = 1,
        size: int = 10,
        sort: Optional[str] = None,
        filter: Optional[str] = None,
        search: Optional[str] = None,
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """Get groups available for assignment to a specific user"""
        try:
            params: Dict[str, Any] = {"page": page, "size": size}
            if sort:
                params["sort"] = sort
            if filter:
                params["filter"] = filter
            if search:
                params["search"] = search
            return self._make_request(
                method="GET",
                endpoint=f"api/v1/users/{user_id}/group-available",
                params=params
            )
        except RequestException as e:
            raise RequestException(f"Failed to list available groups for user {user_id}: {str(e)}")

    def list_user_assigned_groups(
        self,
        user_id: str,
        page: int = 1,
        size: int = 10,
        sort: Optional[str] = None,
        filter: Optional[str] = None,
        search: Optional[str] = None,
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """Get groups currently assigned to a specific user"""
        try:
            params: Dict[str, Any] = {"page": page, "size": size}
            if sort:
                params["sort"] = sort
            if filter:
                params["filter"] = filter
            if search:
                params["search"] = search
            return self._make_request(
                method="GET",
                endpoint=f"api/v1/users/{user_id}/group-mappings",
                params=params
            )
        except RequestException as e:
            raise RequestException(f"Failed to list assigned groups for user {user_id}: {str(e)}")

    def assign_group_to_user(self, user_id: str, group_id: str) -> Union[Dict[str, Any], List[Any], Any]:
        """Assign a single group to a user (PUT, group_id as query param)"""
        try:
            params = {"group_id": group_id}
            resp = self._make_request(
                method="PUT",
                endpoint=f"api/v1/users/{user_id}/group-mappings",
                params=params
            )
            if not isinstance(resp, dict):
                return {"status": "assigned", "user_id": user_id, "group_id": group_id}
            if not resp or resp.get("status_code") == 204:
                return {"status": "assigned", "user_id": user_id, "group_id": group_id}
            return resp
        except RequestException as e:
            raise RequestException(f"Failed to assign group {group_id} to user {user_id}: {str(e)}")

    def delete_group_from_user(self, user_id: str, group_id: str) -> Union[Dict[str, Any], List[Any], Any]:
        """Delete a single group from a user (DELETE, group_id as query param)"""
        try:
            params = {"group_id": group_id}
            resp = self._make_request(
                method="DELETE",
                endpoint=f"api/v1/users/{user_id}/group-mappings",
                params=params
            )
            if not isinstance(resp, dict):
                return {"status": "deleted", "user_id": user_id, "group_id": group_id}
            if not resp or resp.get("status_code") == 204:
                return {"status": "deleted", "user_id": user_id, "group_id": group_id}
            return resp
        except RequestException as e:
            raise RequestException(f"Failed to delete group {group_id} from user {user_id}: {str(e)}")


    # ====================================================================
    # Group Management (CRUD)
    # ====================================================================
    # - List Groups
    # - Create Group
    # - Update Group
    # - Delete Group
    # ====================================================================

    def list_groups(
        self,
        page: int = 1,
        size: int = 10,
        sort: Optional[str] = None,
        filter: Optional[str] = None,
        search: Optional[str] = None,
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """Fetch group list via GET /api/v1/groups"""
        try:
            params: Dict[str, Any] = {"page": page, "size": size}
            if sort:
                params["sort"] = sort
            if filter:
                params["filter"] = filter
            if search:
                params["search"] = search
            return self._make_request(
                method="GET",
                endpoint=f"api/v1/groups",
                params=params
            )
        except RequestException as e:
            raise RequestException(f"Failed to list groups: {str(e)}")

    def create_group(self, group_name: str) -> Union[Dict[str, Any], List[Any], Any]:
        """Create a new group via POST /api/v1/groups"""
        try:
            params = {"group_name": group_name}
            return self._make_request(
                method="POST",
                endpoint=f"api/v1/groups",
                params=params
            )
        except RequestException as e:
            raise RequestException(f"Failed to create group: {str(e)}")

    def update_group(self, group_id: str, group_name: str) -> Union[Dict[str, Any], List[Any], Any]:
        """Update a group via PUT /api/v1/groups/{group_id}"""
        try:
            if not is_valid_uuid(group_id):
                raise ValueError(f"Invalid group_id format: {group_id}")

            payload = {"group_name": group_name}
            resp = self._make_request(
                method="PUT",
                endpoint=f"api/v1/groups/{group_id}",
                json_data=payload
            )
            # Handle 204 No Content
            if not isinstance(resp, dict):
                return {"status": "updated", "id": group_id, "name": group_name}
            if not resp or resp.get("status_code") == 204:
                return {"status": "updated", "id": group_id, "name": group_name}
            return resp
        except RequestException as e:
            raise RequestException(f"Failed to update group {group_id}: {str(e)}")

    def delete_group(self, group_id: str) -> Union[Dict[str, Any], List[Any], Any]:
        """Delete a group via DELETE /api/v1/groups/{group_id}"""
        try:
            if not is_valid_uuid(group_id):
                raise ValueError(f"Invalid group_id format: {group_id}")

            resp = self._make_request(
                method="DELETE",
                endpoint=f"api/v1/groups/{group_id}"
            )
            # Handle 204 No Content
            if not isinstance(resp, dict):
                return {"status": "deleted", "id": group_id}
            if not resp or resp.get("status_code") == 204:
                return {"status": "deleted", "id": group_id}
            return resp
        except RequestException as e:
            raise RequestException(f"Failed to delete group {group_id}: {str(e)}")


    # ====================================================================
    # Cluster Resource
    # ====================================================================
    # - Fetch cluster resource usage/availability
    # ====================================================================

    def get_project_resource_status(self, node_type: str = "task") -> Union[Dict[str, Any], List[Any], Any]:
        """Fetch cluster resource status via GET /api/v1/resources/cluster"""
        try:
            params = {"node_type": node_type}
            return self._make_request(
                method="GET",
                endpoint=f"api/v1/resources/cluster",
                params=params
            )
        except RequestException as e:
            raise RequestException(f"Failed to get resource status: {str(e)}")
