import logging
from typing import List, Optional

from aporia.api.input_validation import is_valid_ids, is_valid_predict_param_list
from aporia.api.types import PredictionIdentifier
from aporia.consts import LOGGER_NAME
from aporia.graphql_client import GraphQLClient

logger = logging.getLogger(LOGGER_NAME)


async def report_actual_batch(
    graphql_client: GraphQLClient,
    model_id: str,
    model_version: str,
    environment: str,
    actuals: List[List[float]],
    user_ids: Optional[List[PredictionIdentifier]] = None,
    aporia_ids: Optional[List[int]] = None,
):
    """Reports a batch of predictions.

    Args:
        graphql_client (GraphQLClient): GraphQL client
        model_id (str): Model ID
        model_version (str): Model version
        environment (str): Environment in which aporia is running.
        actuals (List[List[float]]): Predictions actual values for each prediction
        user_ids (List[PredictionIdentifier], optional): A list of unique identifiers of the prediction,
            which were provided by the the user during the prediction report. Defaults to None.
        aporia_ids (List[int], optional): A list of unique identifiers of the prediction,
            which were automatically generated by Aporia during the prediction report. Defaults to None.
    """
    query = """
        mutation LogActual(
            $modelId: String!,
            $modelVersion: String!,
            $environment: String!,
            $userIds: [PredictionIdentifier],
            $aporiaIds: [BigInt],
            $actuals: [[Float]]!
        ) {
            logActual(
                modelId: $modelId,
                modelVersion: $modelVersion,
                environment: $environment,
                userIds: $userIds,
                aporiaIds: $aporiaIds,
                actuals: $actuals
            ) {
                warnings
            }
        }
    """

    variables = {
        "modelId": model_id,
        "modelVersion": model_version,
        "actuals": actuals,
        "environment": environment,
        "userIds": user_ids,
        "aporiaIds": aporia_ids,
    }

    result = await graphql_client.query_with_retries(query, variables)
    for warning in result["logActual"]["warnings"]:
        logger.warning(warning)


def is_valid_log_actual_input(
    actuals: List[List[float]],
    user_ids: Optional[List[PredictionIdentifier]] = None,
    aporia_ids: Optional[List[int]] = None,
) -> bool:
    """Checks if log_actual_batch input is valid.

    Args:
        actuals (List[List[float]]): Predictions actual values for each prediction
        user_ids (List[PredictionIdentifier], optional): A list of unique identifiers of the prediction,
            which were provided by the the user during the prediction report. Defaults to None.
        aporia_ids (List[int], optional): A list of unique identifiers of the prediction,
            which were automatically generated by Aporia during the prediction report. Defaults to None.

    Returns:
        bool: True if all of the parameters are valid. False otherwise
    """
    if not is_valid_predict_param_list(actuals):
        logger.debug("Invalid input format for actual parameter")
        return False

    if (user_ids is None or len(user_ids) == 0) and (aporia_ids is None or len(aporia_ids) == 0):
        logger.debug("Invalid input: neither user ids nor aporia ids were provided")
        return False

    user_ids_len = 0 if user_ids is None else len(user_ids)
    aporia_ids_len = 0 if aporia_ids is None else len(aporia_ids)

    if user_ids_len > 0 and aporia_ids_len > 0:
        logger.debug("Invalid input: user ids and aporia ids cannot be used together")
        return False

    if is_valid_ids(user_ids, len(actuals)):
        return True
    elif is_valid_ids(aporia_ids, len(actuals)):
        return True
    logger.debug("Invalid input: ids must be a list equal in length to the number of predictions")
    return False
