# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# 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 for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/


import logging
import time

from rhodecode.api import jsonrpc_method, JSONRPCError
from rhodecode.api.exc import JSONRPCValidationError
from rhodecode.api.utils import Optional, OAttr, get_gist_or_error, get_user_or_error, has_superadmin_permission
from rhodecode.model.db import Session, or_
from rhodecode.model.gist import Gist, GistModel

log = logging.getLogger(__name__)


@jsonrpc_method()
def get_gist(request, apiuser, gistid, content=Optional(False)):
    """
    Get the specified gist, based on the gist ID.

    :param apiuser: This is filled automatically from the |authtoken|.
    :type apiuser: AuthUser
    :param gistid: Set the id of the private or public gist
    :type gistid: str
    :param content: Return the gist content. Default is false.
    :type content: Optional(bool)
    """

    gist = get_gist_or_error(gistid)
    content = Optional.extract(content)

    if not has_superadmin_permission(apiuser):
        if gist.gist_owner != apiuser.user_id:
            raise JSONRPCError(f"gist `{gistid}` does not exist")
    data = gist.get_api_data()

    if content:
        from rhodecode.model.gist import GistModel

        rev, gist_files = GistModel().get_gist_files(gistid)
        data["content"] = {x.path: x.str_content for x in gist_files}
    return data


@jsonrpc_method()
def get_gists(request, apiuser, userid=Optional(OAttr("apiuser"))):
    """
    Get all gists for given user. If userid is empty returned gists
    are for user who called the api

    :param apiuser: This is filled automatically from the |authtoken|.
    :type apiuser: AuthUser
    :param userid: user to get gists for
    :type userid: Optional(str or int)
    """

    if not has_superadmin_permission(apiuser):
        # make sure normal user does not pass someone else userid,
        # he is not allowed to do that
        if not isinstance(userid, Optional) and userid != apiuser.user_id:
            raise JSONRPCError("userid is not the same as your user")

    if isinstance(userid, Optional):
        user_id = apiuser.user_id
    else:
        user_id = get_user_or_error(userid).user_id

    gists = []
    _gists = (
        Gist()
        .query()
        .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))
        .filter(Gist.gist_owner == user_id)
        .order_by(Gist.created_on.desc())
    )
    for gist in _gists:
        gists.append(gist.get_api_data())
    return gists


@jsonrpc_method()
def create_gist(
    request,
    apiuser,
    files,
    gistid=Optional(None),
    owner=Optional(OAttr("apiuser")),
    gist_type=Optional(Gist.GIST_PUBLIC),
    lifetime=Optional(-1),
    acl_level=Optional(Gist.ACL_LEVEL_PUBLIC),
    description=Optional(""),
):
    """
    Creates a new Gist.

    :param apiuser: This is filled automatically from the |authtoken|.
    :type apiuser: AuthUser
    :param files: files to be added to the gist. The data structure has
        to match the following example::

          {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}

    :type files: dict
    :param gistid: Set a custom id for the gist
    :type gistid: Optional(str)
    :param owner: Set the gist owner, defaults to api method caller
    :type owner: Optional(str or int)
    :param gist_type: type of gist ``public`` or ``private``
    :type gist_type: Optional(str)
    :param lifetime: time in minutes of gist lifetime
    :type lifetime: Optional(int)
    :param acl_level: acl level for this gist, can be
        ``acl_public`` or ``acl_private`` If the value is set to
        ``acl_private`` only logged in users are able to access this gist.
        If not set it defaults to ``acl_public``.
    :type acl_level: Optional(str)
    :param description: gist description
    :type description: Optional(str)

    Example  output:

    .. code-block:: bash

      id : <id_given_in_input>
      result : {
        "msg": "created new gist",
        "gist": {}
      }
      error :  null

    Example error output:

    .. code-block:: bash

      id : <id_given_in_input>
      result : null
      error :  {
        "failed to create gist"
      }

    """
    from rhodecode.model import validation_schema
    from rhodecode.model.validation_schema.schemas import gist_schema

    if isinstance(owner, Optional):
        owner = apiuser.user_id

    owner = get_user_or_error(owner)

    lifetime = Optional.extract(lifetime)
    schema = gist_schema.GistSchema().bind(
        # bind the given values if it's allowed, however the deferred
        # validator will still validate it according to other rules
        lifetime_options=[lifetime]
    )

    try:
        nodes = gist_schema.nodes_to_sequence(files, colander_node=schema.get("nodes"))

        schema_data = schema.deserialize(
            dict(
                gistid=Optional.extract(gistid),
                description=Optional.extract(description),
                gist_type=Optional.extract(gist_type),
                lifetime=lifetime,
                gist_acl_level=Optional.extract(acl_level),
                nodes=nodes,
            )
        )

        # convert to safer format with just KEYs so we sure no duplicates
        schema_data["nodes"] = gist_schema.sequence_to_nodes(schema_data["nodes"], colander_node=schema.get("nodes"))

    except validation_schema.Invalid as err:
        raise JSONRPCValidationError(colander_exc=err)

    try:
        gist = GistModel().create(
            owner=owner,
            gist_id=schema_data["gistid"],
            description=schema_data["description"],
            gist_mapping=schema_data["nodes"],
            gist_type=schema_data["gist_type"],
            lifetime=schema_data["lifetime"],
            gist_acl_level=schema_data["gist_acl_level"],
        )
        Session().commit()
        return {"msg": "created new gist", "gist": gist.get_api_data()}
    except Exception:
        log.exception("Error occurred during creation of gist")
        raise JSONRPCError("failed to create gist")


@jsonrpc_method()
def delete_gist(request, apiuser, gistid):
    """
    Deletes existing gist

    :param apiuser: filled automatically from apikey
    :type apiuser: AuthUser
    :param gistid: id of gist to delete
    :type gistid: str

    Example output:

    .. code-block:: bash

      id : <id_given_in_input>
      result : {
        "deleted gist ID: <gist_id>",
        "gist": null
      }
      error :  null

    Example error output:

    .. code-block:: bash

      id : <id_given_in_input>
      result : null
      error :  {
        "failed to delete gist ID:<gist_id>"
      }

    """

    gist = get_gist_or_error(gistid)
    if not has_superadmin_permission(apiuser):
        if gist.gist_owner != apiuser.user_id:
            raise JSONRPCError(f"gist `{gistid}` does not exist")

    try:
        GistModel().delete(gist)
        Session().commit()
        return {"msg": f"deleted gist ID:{gist.gist_access_id}", "gist": None}
    except Exception:
        log.exception("Error occured during gist deletion")
        raise JSONRPCError("failed to delete gist ID:%s" % (gist.gist_access_id,))
