# Copyright (C) 2010-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 mock
import pytest

from rhodecode.lib import helpers as h
from rhodecode.model.db import User, Gist
from rhodecode.model.gist import GistModel
from rhodecode.model.meta import Session
from rhodecode.tests import (
    TEST_USER_ADMIN_LOGIN,
    TEST_USER_REGULAR_LOGIN,
    TEST_USER_REGULAR_PASS,
    TestController,
    assert_session_flash,
)
from rhodecode.tests.routes import route_path


class GistUtility(object):
    def __init__(self):
        self._gist_ids = []

    def __call__(
        self,
        f_name: bytes,
        content: bytes = b"some gist",
        lifetime=-1,
        description="gist-desc",
        gist_type="public",
        acl_level=Gist.GIST_PUBLIC,
        owner=TEST_USER_ADMIN_LOGIN,
    ):
        gist_mapping = {f_name: {"content": content}}
        user = User.get_by_username(owner)
        gist = GistModel().create(
            description,
            owner=user,
            gist_mapping=gist_mapping,
            gist_type=gist_type,
            lifetime=lifetime,
            gist_acl_level=acl_level,
        )
        Session().commit()
        self._gist_ids.append(gist.gist_id)
        return gist

    def cleanup(self):
        for gist_id in self._gist_ids:
            gist = Gist.get(gist_id)
            if gist:
                Session().delete(gist)

        Session().commit()


@pytest.fixture()
def create_gist(request):
    gist_utility = GistUtility()
    request.addfinalizer(gist_utility.cleanup)
    return gist_utility


class TestGistsController(TestController):
    def test_index_empty(self, create_gist):
        self.log_user()
        response = self.app.get(route_path("gists_show"))
        response.mustcontain("var gist_data = [];")

    def test_index(self, create_gist):
        self.log_user()
        g1 = create_gist(b"gist1")
        g2 = create_gist(b"gist2", lifetime=1400)
        g3 = create_gist(b"gist3", description="gist3-desc")
        g4 = create_gist(b"gist4", gist_type="private").gist_access_id
        response = self.app.get(route_path("gists_show"))

        response.mustcontain(g1.gist_access_id)
        response.mustcontain(g2.gist_access_id)
        response.mustcontain(g3.gist_access_id)
        response.mustcontain("gist3-desc")
        response.mustcontain(no=[g4])

        # Expiration information should be visible
        expires_tag = str(h.age_component(h.time_to_utcdatetime(g2.gist_expires)))
        response.mustcontain(expires_tag.replace('"', '\\"'))

    def test_index_private_gists(self, create_gist):
        self.log_user()
        gist = create_gist(b"gist5", gist_type="private")
        response = self.app.get(route_path("gists_show", params=dict(private=1)))

        # and privates
        response.mustcontain(gist.gist_access_id)

    def test_index_show_all(self, create_gist):
        self.log_user()
        create_gist(b"gist1")
        create_gist(b"gist2", lifetime=1400)
        create_gist(b"gist3", description="gist3-desc")
        create_gist(b"gist4", gist_type="private")

        response = self.app.get(route_path("gists_show", params=dict(all=1)))

        assert len(GistModel.get_all()) == 4
        # and privates
        for gist in GistModel.get_all():
            response.mustcontain(gist.gist_access_id)

    def test_index_show_all_hidden_from_regular(self, create_gist):
        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
        create_gist(b"gist2", gist_type="private")
        create_gist(b"gist3", gist_type="private")
        create_gist(b"gist4", gist_type="private")

        response = self.app.get(route_path("gists_show", params=dict(all=1)))

        assert len(GistModel.get_all()) == 3
        # since we don't have access to private in this view, we
        # should see nothing
        for gist in GistModel.get_all():
            response.mustcontain(no=[gist.gist_access_id])

    def test_create(self):
        self.log_user()
        response = self.app.post(
            route_path("gists_create"),
            params={
                "lifetime": -1,
                "content": "gist test",
                "filename": "foo",
                "gist_type": "public",
                "gist_acl_level": Gist.ACL_LEVEL_PUBLIC,
                "csrf_token": self.csrf_token,
            },
            status=302,
        )
        response = response.follow()
        response.mustcontain("added file: foo")
        response.mustcontain("gist test")

    def test_create_with_path_with_dirs(self):
        self.log_user()
        response = self.app.post(
            route_path("gists_create"),
            params={
                "lifetime": -1,
                "content": "gist test",
                "filename": "/home/foo",
                "gist_type": "public",
                "gist_acl_level": Gist.ACL_LEVEL_PUBLIC,
                "csrf_token": self.csrf_token,
            },
            status=200,
        )
        response.mustcontain("Filename /home/foo cannot be inside a directory")

    def test_access_expired_gist(self, create_gist):
        self.log_user()
        gist = create_gist(b"never-see-me")
        gist.gist_expires = 0  # 1970
        Session().add(gist)
        Session().commit()

        self.app.get(route_path("gist_show", gist_id=gist.gist_access_id), status=404)

    def test_create_private(self):
        self.log_user()
        response = self.app.post(
            route_path("gists_create"),
            params={
                "lifetime": -1,
                "content": "private gist test",
                "filename": "private-foo",
                "gist_type": "private",
                "gist_acl_level": Gist.ACL_LEVEL_PUBLIC,
                "csrf_token": self.csrf_token,
            },
            status=302,
        )
        response = response.follow()
        response.mustcontain("added file: private-foo<")
        response.mustcontain("private gist test")
        response.mustcontain("Private Gist")
        # Make sure private gists are not indexed by robots
        response.mustcontain('<meta name="robots" content="noindex, nofollow">')

    def test_create_private_acl_private(self):
        self.log_user()
        response = self.app.post(
            route_path("gists_create"),
            params={
                "lifetime": -1,
                "content": "private gist test",
                "filename": "private-foo",
                "gist_type": "private",
                "gist_acl_level": Gist.ACL_LEVEL_PRIVATE,
                "csrf_token": self.csrf_token,
            },
            status=302,
        )
        response = response.follow()
        response.mustcontain("added file: private-foo<")
        response.mustcontain("private gist test")
        response.mustcontain("Private Gist")
        # Make sure private gists are not indexed by robots
        response.mustcontain('<meta name="robots" content="noindex, nofollow">')

    def test_create_with_description(self):
        self.log_user()
        response = self.app.post(
            route_path("gists_create"),
            params={
                "lifetime": -1,
                "content": "gist test",
                "filename": "foo-desc",
                "description": "gist-desc",
                "gist_type": "public",
                "gist_acl_level": Gist.ACL_LEVEL_PUBLIC,
                "csrf_token": self.csrf_token,
            },
            status=302,
        )
        response = response.follow()
        response.mustcontain("added file: foo-desc")
        response.mustcontain("gist test")
        response.mustcontain("gist-desc")

    def test_create_public_with_anonymous_access(self):
        self.log_user()
        params = {
            "lifetime": -1,
            "content": "gist test",
            "filename": "foo-desc",
            "description": "gist-desc",
            "gist_type": "public",
            "gist_acl_level": Gist.ACL_LEVEL_PUBLIC,
            "csrf_token": self.csrf_token,
        }
        response = self.app.post(route_path("gists_create"), params=params, status=302)
        self.logout_user()
        response = response.follow()
        response.mustcontain("added file: foo-desc")
        response.mustcontain("gist test")
        response.mustcontain("gist-desc")

    def test_new(self):
        self.log_user()
        self.app.get(route_path("gists_new"))

    def test_delete(self, create_gist):
        self.log_user()
        gist = create_gist(b"delete-me")
        response = self.app.post(
            route_path("gist_delete", gist_id=gist.gist_id), params={"csrf_token": self.csrf_token}
        )
        assert_session_flash(response, "Deleted gist %s" % gist.gist_id)

    def test_delete_normal_user_his_gist(self, create_gist):
        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
        gist = create_gist(b"delete-me", owner=TEST_USER_REGULAR_LOGIN)

        response = self.app.post(
            route_path("gist_delete", gist_id=gist.gist_id), params={"csrf_token": self.csrf_token}
        )
        assert_session_flash(response, "Deleted gist %s" % gist.gist_id)

    def test_delete_normal_user_not_his_own_gist(self, create_gist):
        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
        gist = create_gist(b"delete-me-2")

        self.app.post(
            route_path("gist_delete", gist_id=gist.gist_id), params={"csrf_token": self.csrf_token}, status=404
        )

    def test_show(self, create_gist):
        gist = create_gist(b"gist-show-me")
        response = self.app.get(route_path("gist_show", gist_id=gist.gist_access_id))

        response.mustcontain("added file: gist-show-me<")

        assert_response = response.assert_response()
        assert_response.element_equals_to("div.rc-user span.user", '<a href="/_profiles/test_admin">test_admin</a>')

        response.mustcontain("gist-desc")

    def test_show_without_hg(self, create_gist):
        with mock.patch("rhodecode.lib.vcs.settings.ALIASES", ["git"]):
            gist = create_gist(b"gist-show-me-again")
            self.app.get(route_path("gist_show", gist_id=gist.gist_access_id), status=200)

    def test_show_acl_private(self, create_gist):
        gist = create_gist(b"gist-show-me-only-when-im-logged-in", acl_level=Gist.ACL_LEVEL_PRIVATE)
        self.app.get(route_path("gist_show", gist_id=gist.gist_access_id), status=404)

        # now we log-in we should see thi gist
        self.log_user()
        response = self.app.get(route_path("gist_show", gist_id=gist.gist_access_id))
        response.mustcontain("added file: gist-show-me-only-when-im-logged-in")

        assert_response = response.assert_response()
        assert_response.element_equals_to("div.rc-user span.user", '<a href="/_profiles/test_admin">test_admin</a>')
        response.mustcontain("gist-desc")

    def test_show_as_raw(self, create_gist):
        gist = create_gist(b"gist-show-me", content=b"GIST CONTENT")
        response = self.app.get(
            route_path("gist_show_formatted", gist_id=gist.gist_access_id, revision="tip", format="raw")
        )
        assert response.text == "GIST CONTENT"

    def test_show_as_raw_individual_file(self, create_gist):
        gist = create_gist(b"gist-show-me-raw", content=b"GIST BODY")
        response = self.app.get(
            route_path(
                "gist_show_formatted_path",
                gist_id=gist.gist_access_id,
                format="raw",
                revision="tip",
                f_path="gist-show-me-raw",
            )
        )
        assert response.text == "GIST BODY"

    def test_edit_page(self, create_gist):
        self.log_user()
        gist = create_gist(b"gist-for-edit", content=b"GIST EDIT BODY")
        response = self.app.get(route_path("gist_edit", gist_id=gist.gist_access_id))
        response.mustcontain("GIST EDIT BODY")

    def test_edit_page_non_logged_user(self, create_gist):
        gist = create_gist(b"gist-for-edit", content=b"GIST EDIT BODY")
        self.app.get(route_path("gist_edit", gist_id=gist.gist_access_id), status=302)

    def test_edit_normal_user_his_gist(self, create_gist):
        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
        gist = create_gist(b"gist-for-edit", owner=TEST_USER_REGULAR_LOGIN)
        self.app.get(route_path("gist_edit", gist_id=gist.gist_access_id, status=200))

    def test_edit_normal_user_not_his_own_gist(self, create_gist):
        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
        gist = create_gist(b"delete-me")
        self.app.get(route_path("gist_edit", gist_id=gist.gist_access_id), status=404)

    def test_user_first_name_is_escaped(self, user_util, create_gist):
        xss_atack_string = "\"><script>alert('First Name')</script>"
        xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
        password = "test"
        user = user_util.create_user(firstname=xss_atack_string, password=password)
        create_gist(b"gist", gist_type="public", owner=user.username)
        response = self.app.get(route_path("gists_show"))
        response.mustcontain(xss_escaped_string)

    def test_user_last_name_is_escaped(self, user_util, create_gist):
        xss_atack_string = "\"><script>alert('Last Name')</script>"
        xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
        password = "test"
        user = user_util.create_user(lastname=xss_atack_string, password=password)
        create_gist(b"gist", gist_type="public", owner=user.username)
        response = self.app.get(route_path("gists_show"))
        response.mustcontain(xss_escaped_string)
