# Copyright (c) 2021, 2024, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0,
# as published by the Free Software Foundation.
#
# This program is designed to work with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms, as
# designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an additional
# permission to link the program and your derivative works with the
# separately licensed software that they have either included with
# the program or referenced in the documentation.
#
# This program 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 General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

from gui_plugin.core.Error import MSGException
import gui_plugin.core.Error as Error


FOLDERS_TREE_SQL = """WITH RECURSIVE
                      folders(id, caption, parent_folder_id) AS (
                        SELECT id, caption, parent_folder_id FROM data_folder
                            WHERE id=?
                        UNION ALL
                        SELECT df.id, df.caption, df.parent_folder_id
                            FROM folders f
                            JOIN data_folder df ON f.id=df.parent_folder_id
                      )
                      SELECT DISTINCT id, caption, parent_folder_id FROM folders"""


def create_folder(db, caption, parent_folder_id=None):
    """Creates folder structures for given path.

    Args:
        db (object): The db object
        caption (str): The caption of the folder
        parent_folder_id (int): The id of the parent folder

    Returns:
        The id of created folder.
    """
    root_folder_id = None
    folder_id = None
    # We have to determine if this is caption or path
    if not caption.startswith("/"):
        db.execute("""INSERT INTO data_folder (caption, parent_folder_id)
                    VALUES(?, ?)""", (caption, parent_folder_id,))
        folder_id = db.get_last_row_id()
        if root_folder_id is None:
            root_folder_id = folder_id
    else:
        parent_id = parent_folder_id
        for folder in caption.split("/")[1:]:
            parent_id, root_folder_id = create_folder(db, folder, parent_id)
        folder_id = parent_id

    return (folder_id, root_folder_id)


def add_data_to_folder(db, data_id, folder_id, read_only):
    """Inserts a new record into the data_folder_has_data table.

    Args:
        db (object): The db object
        data_id (int): The id of the data
        folder_id (int): The id of the folder path
        read_only (int): The flag that specifies whether the data is read only

    Returns:
        None
    """

    db.execute("""INSERT INTO data_folder_has_data
                                (data_id, data_folder_id, read_only)
                    VALUES(?, ?, ?)""", (
        data_id, folder_id, read_only))


def get_user_privileges_for_data(db, data_id, user_id):
    """Get privileges to data for the given user.

        Args:
            db (object): The db object
            data_id (int): The id of the data
            user_id (int): The id of the user

        Returns:
            List of all privileges that user have to data_id.
    """

    privilege_list = []

    ROOT_FOLDERS_SQL = """WITH RECURSIVE
                            path(id, parent_folder_id) AS (
                                SELECT id, parent_folder_id FROM data_folder
                                    WHERE id=?
                                UNION ALL
                                SELECT df.id, df.parent_folder_id
                                    FROM path p
                                    JOIN data_folder df ON df.id=p.parent_folder_id
                            )
                        SELECT DISTINCT id, ?
                        FROM path
                            WHERE parent_folder_id is NULL;"""

    db.execute(
        """CREATE TEMP TABLE root_folders(folder_id INTEGER, read_only TINYINT)""", ())
    res = db.execute("""SELECT data_folder_id, read_only
                        FROM data_folder_has_data
                            WHERE data_id=?;""",
                     (data_id,)).fetch_all()

    for r in res:
        sql = f"""INSERT INTO temp.root_folders {ROOT_FOLDERS_SQL}"""
        db.execute(sql, (r['data_folder_id'], r['read_only']))

    res = db.execute("""SELECT p.id, rf.read_only
                        FROM profile p
                        JOIN data_profile_tree dpf ON dpf.profile_id = p.id
                        JOIN root_folders rf ON rf.folder_id = dpf.root_folder_id
                            WHERE p.user_id = ?;""",
                     (user_id,)).fetch_all()

    for row in res:
        privilege_list.append({"type": "PROFILE",
                               "id": row['id'],
                               "read_only": row['read_only']})

    res = db.execute("""SELECT ug.id, rf.read_only
                        FROM user_group_has_user ughu
                        JOIN user_group ug ON ughu.user_group_id = ug.id
                        JOIN data_user_group_tree dugt ON dugt.user_group_id = ug.id
                        JOIN root_folders rf ON rf.folder_id = dugt.root_folder_id
                            WHERE ughu.user_id = ?;""",
                     (user_id,)).fetch_all()
    for row in res:
        privilege_list.append({"type": "GROUP",
                               "id": row['id'],
                               "read_only": row['read_only']})

    db.execute("""DROP TABLE temp.root_folders""", ())

    if not privilege_list:
        raise MSGException(Error.MODULES_NO_PRIVILEGES_FOUND_FOR_MODULE_DATA,
                           f"User have no privileges for data id '{data_id}'.")

    return privilege_list


def get_profile_owner(db, profile_id):
    """Get owner of the given profile.

        Args:
            db (object): The db object
            profile_id (int): The id of the profile

        Returns:
            The id of the user that owns the profile.
    """
    owner_id = None
    res = db.execute("""SELECT user_id
                        FROM profile
                        WHERE id = ?;""",
                     (profile_id,)).fetch_one()
    if res is None:
        raise MSGException(Error.MODULES_NO_PROFILE_FOUND,
                           f"The is no profile with id {profile_id}")
    else:
        owner_id = res[0]

    return owner_id


def get_root_folder_id(db, tree_identifier, linked_to, link_id):
    """Get id of the root folder for given data category and profile or user group

    Args:
        db (object): The db object
        tree_identifier (str): The identifier of the tree
        linked_to (str): ['profile'|'group']
        link_id (int): The profile id or the group id (depending on linked_to)

    Returns:
        The id of the root folder.
    """

    if linked_to not in ['profile', 'group']:
        raise MSGException(Error.CORE_INVALID_PARAMETER,
                           "Incorrect 'linked_to' value.")

    root_folder_id = None
    SQL_PROFILE = """SELECT root_folder_id
                     FROM data_profile_tree
                     WHERE profile_id=? AND tree_identifier=?"""
    SQL_USER_GROUP = """SELECT root_folder_id
                     FROM data_user_group_tree
                     WHERE user_group_id=? AND tree_identifier=?"""
    sql = SQL_PROFILE if linked_to == 'profile' else SQL_USER_GROUP
    res = db.execute(sql,
                     (link_id, tree_identifier)).fetch_one()
    if res:
        root_folder_id = res['root_folder_id']

    return root_folder_id


def add_data_associations(db, data_id, tree_identifier, folder_path, profile_id, user_group_id):
    """Creates associations to the active user profile
       and personal user group for the given data.

    Args:
        db (object): The db object
        data_id (int): The id of the data
        tree_identifier (str): The identifier of the tree
        folder_path (str): The folder path f.e. "/scripts/server1"
        profile_id (int): The id of profile
        user_group_id (int): The user group id

    Returns:
        None
    """

    profile_root_folder_id = get_root_folder_id(
        db, tree_identifier, 'profile', profile_id)
    user_group_root_folder_id = get_root_folder_id(
        db, tree_identifier, 'group', user_group_id)

    if profile_root_folder_id is None:
        profile_root_folder_id = create_profile_data_tree(
            db, tree_identifier, profile_id)

    if user_group_root_folder_id is None:
        user_group_root_folder_id = create_user_group_data_tree(
            db, tree_identifier, user_group_id)

    if folder_path:
        folder_profile_id = get_folder_id(
            db, profile_root_folder_id, folder_path)
        folder_user_group_id = get_folder_id(
            db, user_group_root_folder_id, folder_path)
        if folder_profile_id is None:
            folder_profile_id, _ = create_folder(
                db, folder_path, profile_root_folder_id)
        if folder_user_group_id is None:
            folder_user_group_id, _ = create_folder(
                db, folder_path, user_group_root_folder_id)
    else:
        folder_profile_id = profile_root_folder_id
        folder_user_group_id = user_group_root_folder_id

    add_data_to_folder(db, data_id, folder_profile_id, read_only=0)
    add_data_to_folder(db, data_id, folder_user_group_id, read_only=0)


def create_user_group_data_tree(db, tree_identifier, user_group_id):
    """Creates the user group data tree for the given tree identifier and profile_id.

    Args:
        db (object): The db object
        tree_identifier (str): The identifier of the tree
        user_group_id (int): The id of user group

    Returns:
        The id of the root folder
    """

    folder_id, _ = create_folder(db, tree_identifier)

    db.execute("""INSERT INTO data_user_group_tree
                  (user_group_id, root_folder_id, tree_identifier)
                  VALUES(?, ?, ?)""",
               (user_group_id, folder_id, tree_identifier))

    return folder_id


def create_profile_data_tree(db, tree_identifier, profile_id):
    """Creates the profile data tree for the given tree identifier and profile_id.

    Args:
        db (object): The db object
        tree_identifier (str): The identifier of the tree
        profile_id (int): The id of profile

    Returns:
        The id of the root folder
    """

    folder_id, _ = create_folder(db, tree_identifier)
    db.execute("""INSERT INTO data_profile_tree
                                (profile_id, root_folder_id, tree_identifier)
                  VALUES(?, ?, ?)""", (
        profile_id, folder_id, tree_identifier))

    return folder_id


def get_folder_id(db, root_folder_id, folder_path):
    """Gets the id of leaf folder in folder path for the given root_folder_id.

    Args:
        db (object): The db object
        root_folder_id (int): The id of root folder
        folder_path (str): The folder path f.e. "/scripts/server1"

    Returns:
        The id of the leaf folder in folder path
    """

    folder_id = None
    sql = f"""{FOLDERS_TREE_SQL} WHERE caption=?"""
    rows = db.execute(
        sql, (root_folder_id, folder_path.split("/")[-1])).fetch_all()
    if rows:
        for row in rows:
            res = db.execute("""WITH RECURSIVE
                                path(id, parent_folder_id, caption) AS (
                                    SELECT id, parent_folder_id, caption FROM data_folder
                                        WHERE id=?
                                    UNION ALL
                                    SELECT df.id, df.parent_folder_id, df.caption
                                        FROM path p
                                        JOIN data_folder df ON df.id=p.parent_folder_id
                                    )
                                SELECT DISTINCT caption FROM path;""",
                             (row['id'],)).fetch_all()
            if res:
                path = [r['caption'] for r in res][::-1][1:]
                if path == folder_path.split("/"):
                    folder_id = row['id']
                    break

    return folder_id


def delete_data(db, id, folder_id):
    """Deletes data

    Args:
        db (object): The db object
        id (int): The id of the data
        folder_id (int): The id of the folder

    Returns:
        The id of the deleted record.
    """
    db.execute("""DELETE FROM data_folder_has_data
                    WHERE data_id=? AND data_folder_id=?""",
               (id, folder_id,))

    if db.rows_affected == 0:
        raise MSGException(
            Error.DB_ERROR, "Cannot delete data from the given folder id.")

    res = db.execute("""SELECT data_id
                        FROM data_folder_has_data
                        WHERE data_id=?
                        LIMIT 1;""",
                     (id,)).fetch_all()
    if res is None:
        db.execute("""DELETE FROM data
                        WHERE id=?""",
                   (id,))

    return id
