# coding: utf-8
#
# Copyright 2014 The Oppia Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Classes for handling events."""

from __future__ import annotations

import logging

from core import feconf
from core.domain import exp_domain
from core.domain import exp_fetchers
from core.domain import feedback_services
from core.domain import stats_domain
from core.domain import stats_services
from core.domain import taskqueue_services
from core.platform import models

(feedback_models, stats_models, user_models) = models.Registry.import_models([
    models.NAMES.feedback, models.NAMES.statistics, models.NAMES.user])

transaction_services = models.Registry.import_transaction_services()


class BaseEventHandler:
    """Base class for event dispatchers."""

    # A string denoting the type of the event. Should be specified by
    # subclasses and considered immutable.
    EVENT_TYPE = None

    @classmethod
    def _handle_event(cls, *args, **kwargs):
        """Perform in-request processing of an incoming event."""
        raise NotImplementedError(
            'Subclasses of BaseEventHandler should implement the '
            '_handle_event() method, using explicit arguments '
            '(no *args or **kwargs).')

    @classmethod
    def record(cls, *args, **kwargs):
        """Process incoming events.

        Callers of event handlers should call this method, not _handle_event().
        """
        cls._handle_event(*args, **kwargs)


class StatsEventsHandler(BaseEventHandler):
    """Event handler for incremental update of analytics model using aggregated
    stats data.
    """

    EVENT_TYPE = feconf.EVENT_TYPE_ALL_STATS

    @classmethod
    def _is_latest_version(cls, exp_id, exp_version):
        """Verifies whether the exploration version for the stats to be stored
        corresponds to the latest version of the exploration.
        """
        exploration = exp_fetchers.get_exploration_by_id(exp_id)
        return exploration.version == exp_version

    @classmethod
    def _handle_event(cls, exploration_id, exp_version, aggregated_stats):
        if 'undefined' in aggregated_stats['state_stats_mapping']:
            logging.error(
                'Aggregated stats contains an undefined state name: %s'
                % list(aggregated_stats['state_stats_mapping'].keys()))
            return
        if cls._is_latest_version(exploration_id, exp_version):
            taskqueue_services.defer(
                taskqueue_services.FUNCTION_ID_UPDATE_STATS,
                taskqueue_services.QUEUE_NAME_STATS,
                exploration_id,
                exp_version, aggregated_stats)


class AnswerSubmissionEventHandler(BaseEventHandler):
    """Event handler for recording answer submissions."""

    EVENT_TYPE = feconf.EVENT_TYPE_ANSWER_SUBMITTED

    @classmethod
    def _handle_event(
            cls, exploration_id, exploration_version, state_name,
            interaction_id, answer_group_index, rule_spec_index,
            classification_categorization, session_id, time_spent_in_secs,
            params, normalized_answer):
        """Records an event when an answer triggers a rule. The answer recorded
        here is a Python-representation of the actual answer submitted by the
        user.
        """
        # TODO(sll): Escape these args?
        stats_services.record_answer(
            exploration_id, exploration_version, state_name, interaction_id,
            stats_domain.SubmittedAnswer(
                normalized_answer, interaction_id, answer_group_index,
                rule_spec_index, classification_categorization, params,
                session_id, time_spent_in_secs))

        feedback_is_useful = (
            classification_categorization != (
                exp_domain.DEFAULT_OUTCOME_CLASSIFICATION))

        stats_models.AnswerSubmittedEventLogEntryModel.create(
            exploration_id, exploration_version, state_name, session_id,
            time_spent_in_secs, feedback_is_useful)