import uuid
from copy import deepcopy
from importlib.resources import files
from typing import Any

from .. import RELAIData
from ..data.data_type import DataType
from .feedback_question import FeedbackQuestion


class FeedbackQuestions(RELAIData):
    """Data model for a feedback questions component.

    Args:
        feedback_questions (list[dict]): List of questions to be asked. Each question must be a dictionary in the
            following format:
            {
                "question": str,
                "type": Type of the question. Must be either a `DataType` object or the string "contextSelection".
                "context": A list of RELAIData components to show as context for the question. Required if the type is
                    "contextSelection".
                "modifies": A list of one or more strings that uniquely determine what needs to be changed in the
                    application result. If not provided, a unique ID will be generated for each question.
            }
        feedback (list[Any], optional): Feedback for each of the questions. In most cases, this will be `None`,
            especially when the component is created in a RELAI application, as the user has not provided feedback
            yet. Defaults to None.
        column_span (int, optional): Number of columns the component should span. Defaults to 12.
    """

    schema_file: str = files("relai_visualization").joinpath("schema/components/feedback-questions.schema.json")

    def __init__(
        self,
        feedback_questions: list[dict],
        feedback: list[Any] = None,
        column_span: int = 12,
    ) -> None:
        if feedback and len(feedback) != len(feedback_questions):
            raise ValueError("Number of feedbacks must match the number of feedback questions.")

        if not feedback:
            feedback = [None] * len(feedback_questions)

        feedback_questions_dict = {}
        feedback_dict = {}
        for question, answer in zip(feedback_questions, feedback):
            question_id = f"question-{uuid.uuid4().hex}"
            question_modifies = [question_id]
            try:
                # Add question ID to the list of `modifies` identifiers
                question_modifies += question["modifies"]
            except KeyError:
                pass
            feedback_questions_dict[question_id] = FeedbackQuestion(
                question=question["question"],
                type=question["type"],
                modifies=question_modifies,
                context=question.get("context"),
            ).data
            feedback_dict[question_id] = answer

        super().__init__(
            componentName="feedback-questions",
            columnSpan=column_span,
            feedbackQuestions=feedback_questions_dict,
            feedback=feedback_dict,
        )

    def custom_validation(self) -> None:
        if self.data["feedback"]:
            for question_idx, (question_id, answer) in enumerate(self.data["feedback"].items()):
                if question_id not in self.data["feedbackQuestions"]:
                    raise ValueError(f"Feedback question with ID {question_id} does not exist.")
                if answer and not DataType.from_dict(self.data["feedbackQuestions"][question_id]["type"]).validate(
                    answer
                ):
                    raise ValueError(f"Invalid feedback {answer} for question {question_idx}.")

    @classmethod
    def user_feedback(cls, component: dict) -> dict:
        """Get the user feedback for the feedback questions.

        Args:
            component (dict): Feedback questions component data as a dictionary.

        Returns:
            dict: User feedback for the feedback questions as a dictionary mapping question to feedback values.
        """
        output = {}
        for question_id, answer in component["feedback"].items():
            output[component["feedbackQuestions"][question_id]["question"]] = answer
        return output

    @classmethod
    def update_with_feedback(cls, component: dict, modifies: list[str], new_value: Any) -> dict:
        """Update the feedback questions component with the new feedback value.

        Args:
            component (dict): Feedback questions component to update.
            modifies (list[str]): Identifiers for the feedback question to update. Only the first element of the list
                is used to identify the question within this component. The rest of the elements may be used to
                identify elements in other components.
            new_value (Any): New feedback value.

        Returns:
            dict: Updated feedback questions component.
        """
        updated_component = deepcopy(component)
        # First element is the question ID, the rest are for other components and are ignored here
        question_id = modifies[0]
        updated_component["feedback"][question_id] = new_value
        return updated_component
