#
#   Copyright © 2021,2022 IsardVDI S.L.
#   Copyright © 2022 Evilham <contact@evilham.com>
#   Copyright © 2022 Elena Barrios Galán @elena61
#
#   This file is part of DD
#
#   DD is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or (at your
#   option) any later version.
#
#   DD is distributed in the hope that it will be useful, but WITHOUT ANY
#   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
#   FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
#   details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with DD. If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: AGPL-3.0-or-later
import logging as log
import traceback
from pprint import pprint

from requests import get, post


from .exceptions import UserExists, UserNotFound
from .postgres import Postgres

from typing import TYPE_CHECKING, cast, Any, Dict, Iterable, List, Optional

if TYPE_CHECKING:
    from admin.flaskapp import AdminFlaskApp

# Module variables to connect to moodle api


class Moodle:
    """https://github.com/mrcinv/moodle_api.py
    https://docs.moodle.org/dev/Web_service_API_functions
    https://docs.moodle.org/311/en/Using_web_services
    """
    key: str
    url : str
    endpoint : str
    verify : bool
    moodle_pg : Postgres
    def __init__(
        self,
        app : "AdminFlaskApp",
        endpoint : str="/webservice/rest/server.php",
    ) -> None:
        self.key = app.config["MOODLE_WS_TOKEN"]
        self.url = f"https://moodle.{ app.config['DOMAIN'] }"
        self.endpoint = endpoint
        self.verify = cast(bool, app.config["VERIFY"])

        self.moodle_pg = Postgres(
            "dd-apps-postgresql",
            "moodle",
            app.config["MOODLE_POSTGRES_USER"],
            app.config["MOODLE_POSTGRES_PASSWORD"],
        )

    def rest_api_parameters(self, in_args : Any, prefix : str="", out_dict : Optional[Dict]=None) -> Dict[Any, Any]:
        """Transform dictionary/array structure to a flat dictionary, with key names
        defining the structure.
        Example usage:
        >>> rest_api_parameters({'courses':[{'id':1,'name': 'course1'}]})
        {'courses[0][id]':1,
         'courses[0][name]':'course1'}
        """
        o : Dict[Any, Any] = {} if out_dict is None else out_dict
        if not type(in_args) in (list, dict):
            o[prefix] = in_args
            return o
        if prefix == "":
            prefix = prefix + "{0}"
        else:
            prefix = prefix + "[{0}]"
        if type(in_args) == list:
            for idx, item in enumerate(in_args):
                self.rest_api_parameters(item, prefix.format(idx), o)
        elif type(in_args) == dict:
            for key, item in in_args.items():
                self.rest_api_parameters(item, prefix.format(key), o)
        return o

    def call(self, fname : str, **kwargs : Any) -> Any:
        """Calls moodle API function with function name fname and keyword arguments.
        Example:
        >>> call_mdl_function('core_course_update_courses',
                               courses = [{'id': 1, 'fullname': 'My favorite course'}])
        """
        parameters = self.rest_api_parameters(kwargs)
        parameters.update(
            {"wstoken": self.key, "moodlewsrestformat": "json", "wsfunction": fname}
        )
        response = post(self.url + self.endpoint, parameters, verify=self.verify)
        response = response.json()
        if type(response) == dict and response.get("exception"):
            raise SystemError(response)
        return response

    def create_user(self, email : str, username : str, password : str, first_name : str="-", last_name : str="-") -> Any:
        if len(self.get_user_by("username", username)["users"]):
            raise UserExists
        try:
            data = [
                {
                    "username": username,
                    "email": email,
                    "password": password,
                    "firstname": first_name,
                    "lastname": last_name,
                }
            ]
            user = self.call("core_user_create_users", users=data)
            return user  # [{'id': 8, 'username': 'asdfw'}]
        except SystemError as se:
            raise SystemError(se.args[0]["message"])

    def update_user(self, username : str, email : str, first_name : str, last_name : str, enabled : bool=True) -> Any:
        user = self.get_user_by("username", username)["users"][0]
        if not len(user):
            raise UserNotFound
        try:
            data = [
                {
                    "id": user["id"],
                    "username": username,
                    "email": email,
                    "firstname": first_name,
                    "lastname": last_name,
                    "suspended": 0 if enabled else 1,
                }
            ]
            user = self.call("core_user_update_users", users=data)
            return user
        except SystemError as se:
            raise SystemError(se.args[0]["message"])

    def delete_user(self, user_id : str) -> Any:
        user = self.call("core_user_delete_users", userids=[user_id])
        return user

    def delete_users(self, userids : List[str]) -> Any:
        user = self.call("core_user_delete_users", userids=userids)
        return user

    def get_user_by(self, key : str, value : str) -> Any:
        criteria = [{"key": key, "value": value}]
        try:
            user = self.call("core_user_get_users", criteria=criteria)
        except:
            raise SystemError("Error calling Moodle API\n", traceback.format_exc())
        return user
        # {'users': [{'id': 8, 'username': 'asdfw', 'firstname': 'afowie', 'lastname': 'aokjdnfwe', 'fullname': 'afowie aokjdnfwe', 'email': 'awfewe@ads.com', 'department': '', 'firstaccess': 0, 'lastaccess': 0, 'auth': 'manual', 'suspended': False, 'confirmed': True, 'lang': 'ca', 'theme': '', 'timezone': '99', 'mailformat': 1, 'profileimageurlsmall': 'https://moodle.mydomain.duckdns.org/theme/image.php/cbe/core/1630941606/u/f2', 'profileimageurl': 'https://DOMAIN/theme/image.php/cbe/core/1630941606/u/f1'}], 'warnings': []}

    def get_users_with_groups_and_roles(self) -> List[Dict[Any, Any]]:
        q = """select u.id as id, username, firstname as first, lastname as last, email, json_agg(h.name) as groups,  json_agg(r.shortname) as roles
        from mdl_user as u
        LEFT JOIN mdl_cohort_members AS hm on hm.userid = u.id
        left join mdl_cohort AS h ON h.id = hm.cohortid 
        left join mdl_role_assignments AS ra ON ra.id = u.id
        left join mdl_role as r on r.id = ra.roleid
        where u.deleted = 0
        group by u.id , username, first, last, email"""
        (headers, users) = self.moodle_pg.select_with_headers(q)
        users_with_lists = [
            list(l[:-2])
            + ([[]] if l[-2] == [None] else [list(set(l[-2]))])
            + ([[]] if l[-1] == [None] else [list(set(l[-1]))])
            for l in users
        ]
        list_dict_users = [dict(zip(headers, r)) for r in users_with_lists]
        return list_dict_users

    ## NOT USED. Too slow
    # def get_users_with_groups_and_roles(self):
    #     users=self.get_user_by('email','%%')['users']
    #     for user in users:
    #         user['groups']=[c['name'] for c in self.get_user_cohorts(user['id'])]
    #         user['roles']=[]
    #     return users

    def enroll_user_to_course(self, user_id : str, course_id : str, role_id : int=5) -> Any:
        # 5 is student
        data = [{"roleid": role_id, "userid": user_id, "courseid": course_id}]
        enrolment = self.call("enrol_manual_enrol_users", enrolments=data)
        return enrolment

    def get_quiz_attempt(self, quiz_id : str, user_id : str) -> Any:
        attempts = self.call(
            "mod_quiz_get_user_attempts", quizid=quiz_id, userid=user_id
        )
        return attempts

    def get_cohorts(self) -> List[Dict[str, Any]]:
        cohorts = self.call("core_cohort_get_cohorts")
        return cast(List[Dict[str, Any]], cohorts)

    def add_system_cohort(self, name : str, description : str ="", visible : bool=True) -> Any:
        bit_visible = 1 if visible else 0
        data = [
            {
                "categorytype": {"type": "system", "value": ""},
                "name": name,
                "idnumber": name,
                "description": description,
                "visible": bit_visible,
            }
        ]
        cohort = self.call("core_cohort_create_cohorts", cohorts=data)
        return cohort

    # def add_users_to_cohort(self,users,cohort):
    #     criteria = [{'key': key, 'value': value}]
    #     user = self.call('core_cohort_add_cohort_members', criteria=criteria)
    #     return user

    def add_user_to_cohort(self, userid : str, cohortid : str) -> Any:
        members = [
            {
                "cohorttype": {"type": "id", "value": cohortid},
                "usertype": {"type": "id", "value": userid},
            }
        ]
        user = self.call("core_cohort_add_cohort_members", members=members)
        return user

    def delete_user_in_cohort(self, userid : str, cohortid : str) -> Any:
        members = [{"cohortid": cohortid, "userid": userid}]
        user = self.call("core_cohort_delete_cohort_members", members=members)
        return user

    def get_cohort_members(self, cohort_ids : str) -> Any:
        members = self.call("core_cohort_get_cohort_members", cohortids=cohort_ids)
        # [0]['userids']
        return members

    def delete_cohorts(self, cohortids : Iterable[str]) -> Any:
        deleted = self.call("core_cohort_delete_cohorts", cohortids=cohortids)
        return deleted

    def get_user_cohorts(self, user_id : str) -> Any:
        user_cohorts = []
        cohorts = self.get_cohorts()
        for cohort in cohorts:
            if user_id in self.get_cohort_members(cohort["id"]):
                user_cohorts.append(cohort)
        return user_cohorts

    def add_user_to_siteadmin(self, user_id : str) -> Any:
        q = """SELECT value FROM mdl_config WHERE name='siteadmins'"""
        value = self.moodle_pg.select(q)[0][0]
        if str(user_id) not in value:
            value = value + "," + str(user_id)
            q = """UPDATE mdl_config SET value = '%s' WHERE name='siteadmins'""" % (
                value
            )
            self.moodle_pg.update(q)
            log.warning(
                "MOODLE:ADDING THE USER TO ADMINS: This needs a purge cache in moodle!"
            )
    def unassign_user_rol(self, user_id, role_id):
        unassignments = [{"roleid": role_id, "userid": user_id, "contextlevel": 'system', "instanceid": 0}]
        return self.call("core_role_unassign_roles", unassignments=unassignments)

    def unassign_user_rol(self, user_id, role_id):
        unassignments = [{"roleid": role_id, "userid": user_id, "contextlevel": 'system', "instanceid": 0}]
        return self.call("core_role_unassign_roles", unassignments=unassignments)

    # def add_role_to_user(self, user_id, role='admin', context='missing'):
    #     if role=='admin':
    #         role_id=1
    #     else:
    #         return False
    #     assignments = [{'roleid': role_id, 'userid': user_id,  'contextid': 0}]
    #     self.call('core_role_assign_roles', assignments=assignments)
    # userid=user_id, role_id=role_id)
    #  'contextlevel': 1,


# define('CONTEXT_SYSTEM', 10);
# define('CONTEXT_USER', 30);
# define('CONTEXT_COURSECAT', 40);
# define('CONTEXT_COURSE', 50);
# define('CONTEXT_MODULE', 70);
# define('CONTEXT_BLOCK', 80);

# 'contextlevel': , 'instanceid'
#   $assignment = array( 'roleid' => $role_id, 'userid' => $user_id, 'contextid' => $context_id );
#   $assignments = array( $assignment );
#   $params = array( 'assignments' => $assignments );

#   $response = call_moodle( 'core_role_assign_roles', $params, $token );
