# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import datetime
import re

from collections import Counter
from contextlib import contextmanager

from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.tests import common


class SurveyCase(common.TransactionCase):

    @classmethod
    def setUpClass(cls):
        super(SurveyCase, cls).setUpClass()

        """ Some custom stuff to make the matching between questions and answers
          :param dict _type_match: dict
            key: question type
            value: (answer type, answer field_name)
        """
        cls._type_match = {
            'text_box': ('text_box', 'value_text_box'),
            'char_box': ('char_box', 'value_char_box'),
            'numerical_box': ('numerical_box', 'value_numerical_box'),
            'scale': ('scale', 'value_scale'),  # similar to numerical_box
            'date': ('date', 'value_date'),
            'datetime': ('datetime', 'value_datetime'),
            'simple_choice': ('suggestion', 'suggested_answer_id'),  # TDE: still unclear
            'multiple_choice': ('suggestion', 'suggested_answer_id'),  # TDE: still unclear
            'matrix': ('suggestion', ('suggested_answer_id', 'matrix_row_id')),  # TDE: still unclear
        }

    # ------------------------------------------------------------
    # ASSERTS
    # ------------------------------------------------------------

    def assertAnswer(self, answer, state, page):
        self.assertEqual(answer.state, state)
        self.assertEqual(answer.last_displayed_page_id, page)

    def assertAnswerLines(self, page, answer, answer_data):
        """ Check answer lines.

          :param dict answer_data:
            key = question ID
            value = {'value': [user input]}
        """
        lines = answer.user_input_line_ids.filtered(lambda l: l.page_id == page)
        answer_count = sum(len(user_input['value']) for user_input in answer_data.values())
        self.assertEqual(len(lines), answer_count)
        for qid, user_input in answer_data.items():
            answer_lines = lines.filtered(lambda l: l.question_id.id == qid)
            question = answer_lines[0].question_id  # TDE note: might have several answers for a given question
            if question.question_type == 'multiple_choice':
                values = user_input['value']
                answer_fname = self._type_match[question.question_type][1]
                self.assertEqual(
                    Counter(getattr(line, answer_fname).id for line in answer_lines),
                    Counter(values))
            elif question.question_type == 'simple_choice':
                [value] = user_input['value']
                answer_fname = self._type_match[question.question_type][1]
                self.assertEqual(getattr(answer_lines, answer_fname).id, value)
            elif question.question_type == 'matrix':
                [value_col, value_row] = user_input['value']
                answer_fname_col = self._type_match[question.question_type][1][0]
                answer_fname_row = self._type_match[question.question_type][1][1]
                self.assertEqual(getattr(answer_lines, answer_fname_col).id, value_col)
                self.assertEqual(getattr(answer_lines, answer_fname_row).id, value_row)
            else:
                [value] = user_input['value']
                answer_fname = self._type_match[question.question_type][1]
                if question.question_type == 'numerical_box':
                    self.assertEqual(getattr(answer_lines, answer_fname), float(value))
                else:
                    self.assertEqual(getattr(answer_lines, answer_fname), value)

    def assertResponse(self, response, status_code, text_bits=None):
        self.assertEqual(response.status_code, status_code)
        for text in text_bits or []:
            self.assertIn(text, response.text)

    # ------------------------------------------------------------
    # DATA CREATION
    # ------------------------------------------------------------

    def _add_question(self, page, name, qtype, **kwargs):
        constr_mandatory = kwargs.pop('constr_mandatory', True)
        constr_error_msg = kwargs.pop('constr_error_msg', 'TestError')

        sequence = kwargs.pop('sequence', False)
        if not sequence:
            sequence = page.question_ids[-1].sequence + 1 if page.question_ids else page.sequence + 1

        base_qvalues = {
            'sequence': sequence,
            'title': name,
            'question_type': qtype,
            'constr_mandatory': constr_mandatory,
            'constr_error_msg': constr_error_msg,
        }
        if qtype in ('simple_choice', 'multiple_choice'):
            base_qvalues['suggested_answer_ids'] = [
                (0, 0, {
                    'value': label['value'],
                    'answer_score': label.get('answer_score', 0),
                    'is_correct': label.get('is_correct', False)
                }) for label in kwargs.pop('labels')
            ]
        elif qtype == 'matrix':
            base_qvalues['matrix_subtype'] = kwargs.pop('matrix_subtype', 'simple')
            base_qvalues['suggested_answer_ids'] = [
                (0, 0, {'value': label['value'], 'answer_score': label.get('answer_score', 0)})
                for label in kwargs.pop('labels')
            ]
            base_qvalues['matrix_row_ids'] = [
                (0, 0, {'value': label['value'], 'answer_score': label.get('answer_score', 0)})
                for label in kwargs.pop('labels_2')
            ]
        else:
            pass
        base_qvalues.update(kwargs)
        question = self.env['survey.question'].create(base_qvalues)
        return question

    def _add_answer(self, survey, partner, **kwargs):
        base_avals = {
            'survey_id': survey.id,
            'partner_id': partner.id if partner else False,
            'email': kwargs.pop('email', False),
        }
        base_avals.update(kwargs)
        return self.env['survey.user_input'].create(base_avals)

    def _add_answer_line(self, question, answer, answer_value, **kwargs):
        qtype = self._type_match.get(question.question_type, (False, False))
        answer_type = kwargs.pop('answer_type', qtype[0])
        answer_fname = kwargs.pop('answer_fname', qtype[1])
        if question.question_type == 'matrix':
            answer_fname = qtype[1][0]

        base_alvals = {
            'user_input_id': answer.id,
            'question_id': question.id,
            'skipped': False,
            'answer_type': answer_type,
        }
        base_alvals[answer_fname] = answer_value
        if 'answer_value_row' in kwargs:
            answer_value_row = kwargs.pop('answer_value_row')
            base_alvals[qtype[1][1]] = answer_value_row

        base_alvals.update(kwargs)
        return self.env['survey.user_input.line'].create(base_alvals)

    # ------------------------------------------------------------
    # UTILS / CONTROLLER ENDPOINTS FLOWS
    # ------------------------------------------------------------

    def _access_start(self, survey):
        return self.url_open('/survey/start/%s' % survey.access_token)

    def _access_page(self, survey, token):
        return self.url_open('/survey/%s/%s' % (survey.access_token, token))

    def _access_begin(self, survey, token):
        url = survey.get_base_url() + '/survey/begin/%s/%s' % (survey.access_token, token)
        return self.opener.post(url=url, json={})

    def _access_submit(self, survey, token, post_data):
        url = survey.get_base_url() + '/survey/submit/%s/%s' % (survey.access_token, token)
        return self.opener.post(url=url, json={'params': post_data})

    def _find_csrf_token(self, text):
        csrf_token_re = re.compile("(input.+csrf_token.+value=\")([a-f0-9]{40}o[0-9]*)", re.MULTILINE)
        return csrf_token_re.search(text).groups()[1]

    def _prepare_post_data(self, question, answers, post_data):
        values = answers if isinstance(answers, list) else [answers]
        if question.question_type == 'multiple_choice':
            for value in values:
                value = str(value)
                if question.id in post_data:
                    if isinstance(post_data[question.id], list):
                        post_data[question.id].append(value)
                    else:
                        post_data[question.id] = [post_data[question.id], value]
                else:
                    post_data[question.id] = value
        else:
            [values] = values
            post_data[question.id] = str(values)
        return post_data

    def _answer_question(self, question, answer, answer_token, csrf_token, button_submit='next'):
        # Employee submits the question answer
        post_data = self._format_submission_data(question, answer, {'csrf_token': csrf_token, 'token': answer_token, 'button_submit': button_submit})
        response = self._access_submit(question.survey_id, answer_token, post_data)
        self.assertResponse(response, 200)

        # Employee is redirected on next question
        response = self._access_page(question.survey_id, answer_token)
        self.assertResponse(response, 200)

    def _answer_page(self, page, answers, answer_token, csrf_token):
        post_data = {}
        for question, answer in answers.items():
            post_data[question.id] = answer.id
        post_data['page_id'] = page.id
        post_data['csrf_token'] = csrf_token
        post_data['token'] = answer_token
        response = self._access_submit(page.survey_id, answer_token, post_data)
        self.assertResponse(response, 200)
        response = self._access_page(page.survey_id, answer_token)
        self.assertResponse(response, 200)

    def _format_submission_data(self, question, answer, additional_post_data):
        post_data = {}
        post_data['question_id'] = question.id
        post_data.update(self._prepare_post_data(question, answer, post_data))
        if question.page_id:
            post_data['page_id'] = question.page_id.id
        post_data.update(**additional_post_data)
        return post_data

    # ------------------------------------------------------------
    # UTILS / TOOLS
    # ------------------------------------------------------------

    def _assert_skipped_question(self, question, survey_user):
        statistics = question._prepare_statistics(survey_user.user_input_line_ids)
        question_data = next(
            (question_data
            for question_data in statistics
            if question_data.get('question') == question),
            False
        )
        self.assertTrue(bool(question_data))
        self.assertEqual(len(question_data.get('answer_input_ids') - question_data.get('answer_input_done_ids')), 1)

    def _create_one_question_per_type(self):
        all_questions = self.env['survey.question']
        for (question_type, dummy) in self.env['survey.question']._fields['question_type'].selection:
            kwargs = {}
            if question_type == 'multiple_choice':
                kwargs['labels'] = [{'value': 'MChoice0'}, {'value': 'MChoice1'}]
            elif question_type == 'simple_choice':
                kwargs['labels'] = [{'value': 'SChoice0'}, {'value': 'SChoice1'}]
            elif question_type == 'matrix':
                kwargs['labels'] = [{'value': 'Column0'}, {'value': 'Column1'}]
                kwargs['labels_2'] = [{'value': 'Row0'}, {'value': 'Row1'}]
            all_questions |= self._add_question(self.page_0, 'Q0', question_type, **kwargs)

        return all_questions

    def _create_one_question_per_type_with_scoring(self):
        all_questions = self.env['survey.question']
        for (question_type, dummy) in self.env['survey.question']._fields['question_type'].selection:
            kwargs = {}
            kwargs['question_type'] = question_type
            if question_type == 'numerical_box':
                kwargs['answer_score'] = 1
                kwargs['answer_numerical_box'] = 5
            elif question_type == 'date':
                kwargs['answer_score'] = 2
                kwargs['answer_date'] = datetime.date(2023, 10, 16)
            elif question_type == 'datetime':
                kwargs['answer_score'] = 3
                kwargs['answer_datetime'] = datetime.datetime(2023, 11, 17, 8, 0, 0)
            elif question_type == 'multiple_choice':
                kwargs['answer_score'] = 4
                kwargs['labels'] = [
                    {'value': 'MChoice0', 'is_correct': True},
                    {'value': 'MChoice1', 'is_correct': True},
                    {'value': 'MChoice2'}
                ]
            elif question_type == 'simple_choice':
                kwargs['answer_score'] = 5
                kwargs['labels'] = [
                    {'value': 'SChoice0', 'is_correct': True},
                    {'value': 'SChoice1'}
                ]
            elif question_type == 'matrix':
                kwargs['labels'] = [{'value': 'Column0'}, {'value': 'Column1'}]
                kwargs['labels_2'] = [{'value': 'Row0'}, {'value': 'Row1'}]
            all_questions |= self._add_question(self.page_0, 'Q0', question_type, **kwargs)

        return all_questions


class TestSurveyCommon(SurveyCase):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        """ Create test data: a survey with some pre-defined questions and various test users for ACL """
        cls.survey_manager = mail_new_test_user(
            cls.env, name='Gustave Doré', login='survey_manager', email='survey.manager@example.com',
            groups='survey.group_survey_manager,base.group_user'
        )

        cls.survey_user = mail_new_test_user(
            cls.env, name='Lukas Peeters', login='survey_user', email='survey.user@example.com',
            groups='survey.group_survey_user,base.group_user'
        )

        cls.user_emp = mail_new_test_user(
            cls.env, name='Eglantine Employee', login='user_emp', email='employee@example.com',
            groups='base.group_user', password='user_emp'
        )

        cls.user_portal = mail_new_test_user(
            cls.env, name='Patrick Portal', login='user_portal', email='portal@example.com',
            groups='base.group_portal'
        )

        cls.user_public = mail_new_test_user(
            cls.env, name='Pauline Public', login='user_public', email='public@example.com',
            groups='base.group_public'
        )

        cls.customer = cls.env['res.partner'].create({
            'name': 'Caroline Customer',
            'email': 'customer@example.com',
        })

        cls.survey = cls.env['survey.survey'].with_user(cls.survey_manager).create({
            'title': 'Test Survey',
            'access_mode': 'public',
            'users_login_required': True,
            'users_can_go_back': False,
        })
        cls.page_0 = cls.env['survey.question'].with_user(cls.survey_manager).create({
            'title': 'First page',
            'survey_id': cls.survey.id,
            'sequence': 1,
            'is_page': True,
            'question_type': False,
        })
        cls.question_ft = cls.env['survey.question'].with_user(cls.survey_manager).create({
            'title': 'Test Free Text',
            'survey_id': cls.survey.id,
            'sequence': 2,
            'question_type': 'text_box',
        })
        cls.question_num = cls.env['survey.question'].with_user(cls.survey_manager).create({
            'title': 'Test NUmerical Box',
            'survey_id': cls.survey.id,
            'sequence': 3,
            'question_type': 'numerical_box',
        })
        cls.question_scale = cls.env['survey.question'].with_user(cls.survey_manager).create({
            'title': 'Test Scale',
            'survey_id': cls.survey.id,
            'sequence': 40,
            'question_type': 'scale',
        })


class TestSurveyResultsCommon(SurveyCase):

    @classmethod
    def setUpClass(cls):
        super(TestSurveyResultsCommon, cls).setUpClass()
        cls.survey_manager = mail_new_test_user(
            cls.env, name='Gustave Doré', login='survey_manager', email='survey.manager@example.com',
            groups='survey.group_survey_manager,base.group_user'
        )

        # Create survey with questions
        cls.survey = cls.env['survey.survey'].create({
            'title': 'Test Survey Results',
            'questions_layout': 'one_page'
        })
        cls.question_char_box = cls._add_question(
            cls, None, 'What is your name', 'char_box', survey_id=cls.survey.id, sequence='1')
        cls.question_numerical_box = cls._add_question(
            cls, None, 'What is your age', 'numerical_box', survey_id=cls.survey.id, sequence='2')
        cls.question_sc = cls._add_question(
            cls, None, 'Are you a cat or a dog person', 'simple_choice', survey_id=cls.survey.id,
            sequence='3', labels=[{'value': 'Cat'},
                                    {'value': 'Dog'}])
        cls.question_mc = cls._add_question(
            cls, None, 'What do you like most in our tarte al djotte', 'multiple_choice', survey_id=cls.survey.id,
            sequence='4', labels=[{'value': 'The gras'},
                                    {'value': 'The bette'},
                                    {'value': 'The tout'},
                                    {'value': 'The regime is fucked up'}])
        cls.question_mx1 = cls._add_question(
            cls, None, 'When do you harvest those fruits', 'matrix', survey_id=cls.survey.id, sequence='5',
            labels=[{'value': 'Spring'}, {'value': 'Summer'}],
            labels_2=[{'value': 'Apples'},
                        {'value': 'Strawberries'}])
        cls.question_mx2 = cls._add_question(
            cls, None, 'How often should you water those plants', 'matrix', survey_id=cls.survey.id, sequence='6',
            labels=[{'value': 'Once a month'}, {'value': 'Once a week'}],
            labels_2=[{'value': 'Cactus'},
                        {'value': 'Ficus'}])
        cls.question_scale = cls._add_question(
            cls, None, 'How would you rate your experience on our website ?', 'scale', survey_id=cls.survey.id, sequence='7',
        )

        # Question answers ids
        [cls.cat_id, cls.dog_id] = cls.question_sc.suggested_answer_ids.ids
        [cls.gras_id, cls.bette_id, _, _] = cls.question_mc.suggested_answer_ids.ids
        [cls.apples_row_id, cls.strawberries_row_id] = cls.question_mx1.matrix_row_ids.ids
        [cls.spring_id, cls.summer_id] = cls.question_mx1.suggested_answer_ids.ids
        [cls.cactus_row_id, cls.ficus_row_id] = cls.question_mx2.matrix_row_ids.ids
        [cls.once_a_month_id, cls.once_a_week_id] = cls.question_mx2.suggested_answer_ids.ids

        # Populate survey with answers
        cls.user_input_1 = cls._add_answer(cls, cls.survey, cls.survey_manager.partner_id)
        cls.answer_lukas = cls._add_answer_line(cls, cls.question_char_box, cls.user_input_1, 'Lukas')
        cls.answer_24 = cls._add_answer_line(cls, cls.question_numerical_box, cls.user_input_1, 24)
        cls.answer_cat = cls._add_answer_line(cls, cls.question_sc, cls.user_input_1, cls.cat_id)
        cls._add_answer_line(cls, cls.question_mc, cls.user_input_1, cls.gras_id)
        cls._add_answer_line(cls, cls.question_mx1, cls.user_input_1, cls.summer_id, **{'answer_value_row': cls.apples_row_id})
        cls._add_answer_line(cls, cls.question_mx1, cls.user_input_1, cls.spring_id, **{'answer_value_row': cls.strawberries_row_id})
        cls._add_answer_line(cls, cls.question_mx2, cls.user_input_1, cls.once_a_month_id, **{'answer_value_row': cls.cactus_row_id})
        cls._add_answer_line(cls, cls.question_mx2, cls.user_input_1, cls.once_a_week_id, **{'answer_value_row': cls.ficus_row_id})
        cls._add_answer_line(cls, cls.question_scale, cls.user_input_1, '5')
        cls.user_input_1.state = 'done'

        cls.user_input_2 = cls._add_answer(cls, cls.survey, cls.survey_manager.partner_id)
        cls.answer_pauline = cls._add_answer_line(cls, cls.question_char_box, cls.user_input_2, 'Pauline')
        cls._add_answer_line(cls, cls.question_numerical_box, cls.user_input_2, 24)
        cls.answer_dog = cls._add_answer_line(cls, cls.question_sc, cls.user_input_2, cls.dog_id)
        cls._add_answer_line(cls, cls.question_mc, cls.user_input_2, cls.gras_id)
        cls._add_answer_line(cls, cls.question_mc, cls.user_input_2, cls.bette_id)
        cls._add_answer_line(cls, cls.question_mx1, cls.user_input_2, cls.spring_id, **{'answer_value_row': cls.apples_row_id})
        cls._add_answer_line(cls, cls.question_mx1, cls.user_input_2, cls.spring_id, **{'answer_value_row': cls.strawberries_row_id})
        cls._add_answer_line(cls, cls.question_mx2, cls.user_input_2, cls.once_a_month_id, **{'answer_value_row': cls.cactus_row_id})
        cls._add_answer_line(cls, cls.question_mx2, cls.user_input_2, cls.once_a_month_id, **{'answer_value_row': cls.ficus_row_id})
        cls.scale_answer_line_2 = cls._add_answer_line(cls, cls.question_scale, cls.user_input_1, '7')
        cls.user_input_2.state = 'done'
