from typing import List, Optional, Union

from galtea.domain.exceptions.entity_not_found_exception import EntityNotFoundException
from galtea.domain.models.user_group import UserGroup, UserGroupBase, UserGroupType
from galtea.infrastructure.clients.http_client import Client
from galtea.utils.sort_order import SortOrder, normalize_sort_order
from galtea.utils.string import build_query_params, is_valid_id
from galtea.utils.timestamp import normalize_timestamp


class UserGroupService:
    """
    Service for managing User Groups.
    A User Group organizes users and metrics for evaluation workflows.
    """

    def __init__(self, client: Client):
        self.__client = client

    def create(
        self,
        name: str,
        type: Union[str, UserGroupType] = UserGroupType.EVALUATOR,
        description: Optional[str] = None,
    ) -> Optional[UserGroup]:
        """
        Create a new user group.

        Args:
            name (str): Name of the user group.
            type (str | UserGroupType): Type of user group. Defaults to UserGroupType.EVALUATOR.
            description (str, optional): Description of the user group.

        Returns:
            Optional[UserGroup]: The created user group, or None if an error occurs.
        """
        type = type.value if isinstance(type, UserGroupType) else type

        try:
            user_group = UserGroupBase(
                name=name,
                type=type,
                description=description,
            )
            user_group.model_validate(user_group.model_dump())
            response = self.__client.post("userGroups", json=user_group.model_dump(by_alias=True))
            return UserGroup(**response.json())
        except Exception as e:
            print(f"Error creating UserGroup: {e}")
            return None

    def get(self, user_group_id: str) -> UserGroup:
        """
        Retrieve a user group by its ID.

        Args:
            user_group_id (str): ID of the user group to retrieve.

        Returns:
            UserGroup: The retrieved user group object.
        """
        if not is_valid_id(user_group_id):
            raise ValueError("User group ID provided is not valid.")

        response = self.__client.get(f"userGroups/{user_group_id}")
        return UserGroup(**response.json())

    def get_by_name(self, name: str) -> UserGroup:
        """
        Retrieve a user group by its name.

        Args:
            name (str): Name of the user group to retrieve.

        Returns:
            UserGroup: The retrieved user group object.
        """
        query_params = build_query_params(names=[name])
        response = self.__client.get(f"userGroups?{query_params}")
        user_groups = [UserGroup(**ug) for ug in response.json()]

        if not user_groups:
            raise EntityNotFoundException(f"User group with name {name} does not exist.")

        return user_groups[0]

    def update(
        self,
        user_group_id: str,
        name: Optional[str] = None,
        description: Optional[str] = None,
    ) -> UserGroup:
        """
        Update a user group.

        Args:
            user_group_id (str): ID of the user group to update.
            name (str, optional): New name for the user group.
            description (str, optional): New description for the user group.

        Returns:
            UserGroup: The updated user group object.
        """
        if not is_valid_id(user_group_id):
            raise ValueError("User group ID provided is not valid.")

        payload = {}
        if name is not None:
            payload["name"] = name
        if description is not None:
            payload["description"] = description

        response = self.__client.patch(f"userGroups/{user_group_id}", json=payload)
        return UserGroup(**response.json())

    def delete(self, user_group_id: str) -> None:
        """
        Delete a user group by its ID.

        Args:
            user_group_id (str): ID of the user group to delete.
        """
        if not is_valid_id(user_group_id):
            raise ValueError("User group ID provided is not valid.")

        self.__client.delete(f"userGroups/{user_group_id}")

    def list(
        self,
        organization_id: Optional[str] = None,
        from_created_at: Optional[Union[str, int]] = None,
        to_created_at: Optional[Union[str, int]] = None,
        sort_by_created_at: Optional[Union[str, SortOrder]] = None,
        offset: Optional[int] = None,
        limit: Optional[int] = None,
    ) -> List[UserGroup]:
        """
        Get a list of user groups.

        Args:
            organization_id (str, optional): Filter by organization ID.
            from_created_at (str | int, optional): Filter groups created at or after this timestamp.
                Accepts ISO 8601 string or Unix timestamp (seconds).
            to_created_at (str | int, optional): Filter groups created at or before this timestamp.
                Accepts ISO 8601 string or Unix timestamp (seconds).
            sort_by_created_at (str | SortOrder, optional): Sort by created at. Valid values: 'asc', 'desc'.
            offset (int, optional): Offset for pagination.
            limit (int, optional): Limit for pagination.

        Returns:
            List[UserGroup]: List of user groups.
        """
        sort_by_created_at = normalize_sort_order(sort_by_created_at) if sort_by_created_at is not None else None

        from_created_at_normalized = normalize_timestamp(from_created_at)
        to_created_at_normalized = normalize_timestamp(to_created_at)

        query_params = build_query_params(
            organizationIds=organization_id,
            fromCreatedAt=from_created_at_normalized,
            toCreatedAt=to_created_at_normalized,
            offset=offset,
            limit=limit,
            sort=["createdAt", sort_by_created_at] if sort_by_created_at else None,
        )
        response = self.__client.get(f"userGroups?{query_params}")
        return [UserGroup(**ug) for ug in response.json()]

    def link_users(self, user_group_id: str, user_ids: List[str]) -> None:
        """
        Link users to a user group.

        Args:
            user_group_id (str): ID of the user group.
            user_ids (list[str]): List of user IDs to link.
        """
        if not is_valid_id(user_group_id):
            raise ValueError("User group ID provided is not valid.")

        for user_id in user_ids:
            if not is_valid_id(user_id):
                raise ValueError(f"User ID '{user_id}' is not valid.")

        self.__client.post(f"userGroups/{user_group_id}/users", json={"userIds": user_ids})

    def unlink_users(self, user_group_id: str, user_ids: List[str]) -> None:
        """
        Unlink users from a user group.

        Args:
            user_group_id (str): ID of the user group.
            user_ids (list[str]): List of user IDs to unlink.
        """
        if not is_valid_id(user_group_id):
            raise ValueError("User group ID provided is not valid.")

        for user_id in user_ids:
            if not is_valid_id(user_id):
                raise ValueError(f"User ID '{user_id}' is not valid.")

        self.__client.delete(f"userGroups/{user_group_id}/users", json={"userIds": user_ids})

    def link_metrics(self, user_group_id: str, metric_ids: List[str]) -> None:
        """
        Link metrics to a user group.

        Args:
            user_group_id (str): ID of the user group.
            metric_ids (list[str]): List of metric IDs to link.
        """
        if not is_valid_id(user_group_id):
            raise ValueError("User group ID provided is not valid.")

        for metric_id in metric_ids:
            if not is_valid_id(metric_id):
                raise ValueError(f"Metric ID '{metric_id}' is not valid.")

        self.__client.post(f"userGroups/{user_group_id}/metrics", json={"metricIds": metric_ids})

    def unlink_metrics(self, user_group_id: str, metric_ids: List[str]) -> None:
        """
        Unlink metrics from a user group.

        Args:
            user_group_id (str): ID of the user group.
            metric_ids (list[str]): List of metric IDs to unlink.
        """
        if not is_valid_id(user_group_id):
            raise ValueError("User group ID provided is not valid.")

        for metric_id in metric_ids:
            if not is_valid_id(metric_id):
                raise ValueError(f"Metric ID '{metric_id}' is not valid.")

        self.__client.delete(f"userGroups/{user_group_id}/metrics", json={"metricIds": metric_ids})
