#
#   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 json
import logging as log
import os
import random

import string
import time
import traceback
from datetime import datetime, timedelta

import psycopg2
import yaml

from typing import TYPE_CHECKING, List, Set

if TYPE_CHECKING:
    from admin.flaskapp import AdminFlaskApp

from .postgres import Postgres


def moodle_functions() -> Set[str]:
    return set(
        [
            "core_course_update_courses",
            "core_user_get_users",
            "core_user_get_users_by_field",
            "core_user_update_picture",
            "core_user_update_users",
            "core_user_delete_users",
            "core_user_create_users",
            "core_cohort_get_cohort_members",
            "core_cohort_add_cohort_members",
            "core_cohort_delete_cohort_members",
            "core_cohort_create_cohorts",
            "core_cohort_delete_cohorts",
            "core_cohort_search_cohorts",
            "core_cohort_update_cohorts",
            "core_role_assign_roles",
            "core_role_unassign_roles",
            "core_cohort_get_cohorts",
        ]
    )


class Postup:
    def __init__(self, app: "AdminFlaskApp") -> None:
        ready = False
        while not ready:
            try:
                self.pg = Postgres(
                    "dd-apps-postgresql",
                    "moodle",
                    app.config["MOODLE_POSTGRES_USER"],
                    app.config["MOODLE_POSTGRES_PASSWORD"],
                )
                ready = True
            except:
                log.warning("Could not connect to moodle database. Retrying...")
                time.sleep(2)
        log.info("Connected to moodle database.")

        ready = False
        while not ready:
            try:
                with open(
                    os.path.join(
                        app.root_path,
                        "../moodledata/saml2/moodle." + app.config["DOMAIN"] + ".crt",
                    ),
                    "r",
                ) as crt:
                    app.config.setdefault("SP_CRT", crt.read())
                    ready = True
            except IOError:
                log.warning("Could not get moodle SAML2 crt certificate. Retrying...")
                time.sleep(2)
            except:
                log.error(traceback.format_exc())
        log.info("Got moodle srt certificate.")

        ready = False
        while not ready:
            try:
                with open(
                    os.path.join(
                        app.root_path,
                        "../moodledata/saml2/moodle." + app.config["DOMAIN"] + ".pem",
                    ),
                    "r",
                ) as pem:
                    app.config.setdefault("SP_PEM", pem.read())
                    ready = True
            except IOError:
                log.warning("Could not get moodle SAML2 pem certificate. Retrying...")
                time.sleep(2)
        log.info("Got moodle pem certificate.")

        self.select_and_configure_theme()
        self.configure_tipnc()
        self.add_moodle_ws_token(app)

    def select_and_configure_theme(self, theme: str = "cbe") -> None:
        try:
            self.pg.update(
                """UPDATE "mdl_config" SET value = '%s' WHERE "name" = 'theme';"""
                % (theme)
            )
        except:
            log.error(traceback.format_exc())
            exit(1)

        try:
            self.pg.update(
                """UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'theme_cbe' AND "name" = 'host';"""
                % (os.environ["DOMAIN"])
            )
            self.pg.update(
                """UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'theme_cbe' AND "name" = 'logourl';"""
                % ("https://api." + os.environ["DOMAIN"] + "/img/logo.png")
            )
            self.pg.update(
                """UPDATE "mdl_config_plugins" SET value = '1' WHERE "plugin" = 'theme_cbe' AND "name" = 'header_api';"""
            )
            self.pg.update(
                """UPDATE "mdl_config_plugins" SET value = '1' WHERE "plugin" = 'theme_cbe' AND "name" = 'vclasses_direct';"""
            )
            self.pg.update(
                """UPDATE "mdl_config_plugins" SET value = '1' WHERE "plugin" = 'theme_cbe' AND "name" = 'uniquenamecourse';"""
            )
        except:
            log.error(traceback.format_exc())
            exit(1)

    def configure_tipnc(self) -> None:
        try:
            self.pg.update(
                """UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'host';"""
                % ("https://nextcloud." + os.environ["DOMAIN"] + "/")
            )
            self.pg.update(
                """UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'password';"""
                % (os.environ["NEXTCLOUD_ADMIN_PASSWORD"])
            )
            self.pg.update(
                """UPDATE "mdl_config_plugins" SET value = 'template.docx' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'template';"""
            )
            self.pg.update(
                """UPDATE "mdl_config_plugins" SET value = '/apps/onlyoffice/' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'location';"""
            )
            self.pg.update(
                """UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'user';"""
                % (os.environ["NEXTCLOUD_ADMIN_USER"])
            )
            self.pg.update(
                """UPDATE "mdl_config_plugins" SET value = 'tasks' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'folder';"""
            )
        except:
            log.error(traceback.format_exc())
            exit(1)

    def add_moodle_ws_token(self, app: "AdminFlaskApp") -> None:
        srv_id = 3
        try:
            mdl_service = self.pg.select(
                """SELECT name FROM "mdl_external_services" WHERE name='dd admin';"""
            )
            if not mdl_service:
                self.pg.update(
                    """INSERT INTO "mdl_external_services" ("name", "enabled", "requiredcapability", "restrictedusers", "component", "timecreated", "timemodified", "shortname", "downloadfiles", "uploadfiles") VALUES
                ('dd admin',	1,	'',	1,	NULL,	1621719763,	1621719850,	'dd_admin',	0,	0);"""
                )

            active_functions_res = self.pg.select(
                'SELECT functionname FROM "mdl_external_services_functions" '
                f'WHERE "externalserviceid" = {srv_id}'
            )
            active_functions: List[str] = [
                a[0] if a and isinstance(a, tuple) else a for a in active_functions_res
            ]
            missing_functions = moodle_functions().difference(active_functions)
            if missing_functions:
                missing_functions_values = ", ".join(
                    (f"({srv_id}, '{f}')" for f in missing_functions)
                )
                self.pg.update(
                    'INSERT INTO "mdl_external_services_functions" '
                    '("externalserviceid", "functionname") '
                    f"VALUES {missing_functions_values};"
                )

            mdl_service_user = self.pg.select(
                f"SELECT externalserviceid FROM mdl_external_services_users WHERE externalserviceid={srv_id}"
            )
            if not mdl_service_user:
                self.pg.update(
                    """INSERT INTO "mdl_external_services_users" ("externalserviceid", "userid", "iprestriction", "validuntil", "timecreated") VALUES
                (3,	2,	NULL,	NULL,	1621719871);"""
                )

            token_results = self.pg.select(
                f"""SELECT token FROM "mdl_external_tokens" WHERE "externalserviceid" = {srv_id}"""
            )

            if token_results:
                token = token_results[0][0]
                app.config.setdefault("MOODLE_WS_TOKEN", token)
                return

            b32 = "".join(
                random.choices(
                    string.ascii_uppercase
                    + string.ascii_uppercase
                    + string.ascii_lowercase,
                    k=32,
                )
            )
            b64 = "".join(
                random.choices(
                    string.ascii_uppercase
                    + string.ascii_uppercase
                    + string.ascii_lowercase,
                    k=64,
                )
            )
            self.pg.update(
                """INSERT INTO "mdl_external_tokens" ("token", "privatetoken", "tokentype", "userid", "externalserviceid", "sid", "contextid", "creatorid", "iprestriction", "validuntil", "timecreated", "lastaccess") VALUES
            ('%s',	'%s',	0,	2,	3,	NULL,	1,	2,	NULL,	0,	1621831206,	NULL);"""
                % (b32, b64)
            )

            app.config.setdefault("MOODLE_WS_TOKEN", b32)
        except:
            log.error(traceback.format_exc())
            exit(1)
