"""
Performance tests for the OpenAssessment XBlock.
"""

import os
import json
import random
from lxml import etree
import loremipsum
from locust import HttpLocust, TaskSet, task


class OpenAssessmentPage(object):
    """
    Encapsulate interactions with the OpenAssessment XBlock's pages.
    """

    # These assume that the course fixture has been installed
    COURSE_ID = "tim/1/1"
    BASE_URL = "courses/tim/1/1/courseware/efa85eb090164a208d772a344df7181d/69f15a02c5af4e95b9c5525771b8f4ee/"
    BASE_HANDLER_URL = "courses/tim/1/1/xblock/i4x:;_;_tim;_1;_openassessment;_0e2bbf6cc89e45d98b028fa4e2d46314/handler/"
    OPTIONS_SELECTED = {
        "Ideas": "Good",
        "Content": "Excellent",
    }

    def __init__(self, client):
        """
        Initialize the page to use specified HTTP client.

        Args:
            client (HttpSession): The HTTP client to use.
        """
        self.client = client

        # Configure basic auth
        if 'BASIC_AUTH_USER' in os.environ and 'BASIC_AUTH_PASSWORD' in os.environ:
            self.client.auth = (os.environ['BASIC_AUTH_USER'], os.environ['BASIC_AUTH_PASSWORD'])

        self.step_resp_dict = dict()

    def log_in(self):
        """
        Log in as a unique user with access to the XBlock(s) under test.
        """
        self.client.get("auto_auth", params={'course_id': self.COURSE_ID}, verify=False)
        return self

    def load_steps(self):
        """
        Load all steps in the OpenAssessment flow.
        """
        # Load the container page
        self.client.get(self.BASE_URL, verify=False)

        # Load each of the steps
        step_dict = {
            'submission': 'render_submission',
            'peer': 'render_peer_assessment',
            'self': 'render_self_assessment',
            'grade': 'render_grade',
        }

        self.step_resp_dict = {
            name: self.client.get(self.handler_url(handler), verify=False)
            for name, handler in step_dict.iteritems()
        }

        return self

    def can_submit_response(self):
        """
        Check whether we're allowed to submit a response.
        Should be called after steps have been loaded.

        Returns:
            bool
        """
        resp = self.step_resp_dict.get('submission')
        return resp is not None and resp.content is not None and 'id="submission__answer__value"' in resp.content.lower()

    def can_peer_assess(self):
        """
        Check whether we're allowed to assess a peer.
        Should be called after steps have been loaded.

        Returns:
            bool
        """
        resp = self.step_resp_dict.get('peer')
        return resp is not None and resp.content is not None and 'class="assessment__fields"' in resp.content.lower()

    def can_self_assess(self):
        """
        Check whether we're allowed to self-assess.
        Should be called after steps have been loaded.

        Returns:
            bool
        """
        resp = self.step_resp_dict.get('self')
        return resp is not None and resp.content is not None and 'class="assessment__fields"' in resp.content.lower()

    def submit_response(self):
        """
        Submit a response.
        """
        payload = json.dumps({
            'submission': loremipsum.get_paragraphs(random.randint(1, 10)),
        })
        self.client.post(self.handler_url('submit'), data=payload, headers=self._post_headers, verify=False)

    def peer_assess(self):
        """
        Assess a peer.
        """
        payload = json.dumps({
            'submission_uuid': self._submission_uuid('peer'),
            'options_selected': self.OPTIONS_SELECTED,
            'feedback': loremipsum.get_paragraphs(random.randint(1, 3)),
        })
        self.client.post(self.handler_url('peer_assess'), data=payload, headers=self._post_headers, verify=False)

    def self_assess(self):
        """
        Complete a self-assessment.
        """
        payload = json.dumps({
            'submission_uuid': self._submission_uuid('self'),
            'options_selected': self.OPTIONS_SELECTED,
        })
        self.client.post(self.handler_url('self_assess'), data=payload, headers=self._post_headers, verify=False)

    def handler_url(self, handler_name):
        """
        Return the full URL for an XBlock handler.

        Args:
            handler_name (str): The name of the XBlock handler method.

        Returns:
            str
        """
        return "{base}{handler}".format(base=self.BASE_HANDLER_URL, handler=handler_name)

    def _submission_uuid(self, step):
        """
        Retrieve the submission UUID from the DOM.

        Args:
            step (str): Either "peer" or "self"

        Returns:
            str or None
        """
        resp = self.step_resp_dict.get(step)
        if resp is None:
            return None

        # There might be a faster way to do this
        root = etree.fromstring(resp.content)
        xpath_sel = "span[@id=\"{step}_submission_uuid\"]".format(step=step)
        submission_id_el = root.find(xpath_sel)
        if submission_id_el is not None:
            return submission_id_el.text.strip()
        else:
            return None

    @property
    def _post_headers(self):
        """
        Headers for a POST request, including the CSRF token.
        """
        return {
            'Content-type': 'application/json',
            'Accept': 'application/json',
            'X-CSRFToken': self.client.cookies.get('csrftoken', ''),
        }


class OpenAssessmentTasks(TaskSet):
    """
    Virtual user interactions with the OpenAssessment XBlock.
    """

    def __init__(self, *args, **kwargs):
        """
        Initialize the task set.
        """
        super(OpenAssessmentTasks, self).__init__(*args, **kwargs)
        self.page = OpenAssessmentPage(self.client)

    def on_start(self):
        """
        Log in as a unique user.
        """
        self.page.log_in()

    @task
    def workflow(self):
        """
        Submit a response, if we're allowed to.
        """
        self.page.load_steps()

        if self.page.can_submit_response():
            self.page.submit_response()

        if self.page.can_peer_assess():
            self.page.peer_assess()

        if self.page.can_self_assess():
            self.page.self_assess()
            self.page.log_in()


class OpenAssessmentLocust(HttpLocust):
    """
    Performance test definition for the OpenAssessment XBlock.
    """
    task_set = OpenAssessmentTasks
    min_wait = 10000
    max_wait = 15000