# Copyright (C) 2010-2023 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 os

import mock
import pytest
from whoosh import query

from rhodecode.tests import (
    TestController, route_path_generator, HG_REPO,
    TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
from rhodecode.tests.utils import AssertResponse


def route_path(name, params=None, **kwargs):
    from rhodecode.apps._base import ADMIN_PREFIX
    url_defs = {
        'search':
            ADMIN_PREFIX + '/search',
        'search_repo':
            '/{repo_name}/search',
    }
    return route_path_generator(url_defs, name=name, params=params, **kwargs)


class TestSearchController(TestController):

    def test_index(self):
        self.log_user()
        response = self.app.get(route_path('search'))
        assert_response = response.assert_response()
        assert_response.one_element_exists('input#q')

    def test_search_files_empty_search(self):
        if os.path.isdir(self.index_location):
            pytest.skip('skipped due to existing index')
        else:
            self.log_user()
            response = self.app.get(route_path('search'),
                                    {'q': HG_REPO})
            response.mustcontain('There is no index to search in. '
                                 'Please run whoosh indexer')

    def test_search_validation(self):
        self.log_user()
        response = self.app.get(route_path('search'),
            {'q': query, 'type': 'content', 'page_limit': 1000})

        response.mustcontain(
            'page_limit - 1000 is greater than maximum value 500')

    @pytest.mark.parametrize("query, expected_hits, expected_paths", [
        ('todo', 23, [
            'vcs/backends/hg/inmemory.py',
            'vcs/tests/test_git.py']),
        ('extension:rst installation', 6, [
            'docs/index.rst',
            'docs/installation.rst']),
        ('def repo', 87, [
            'vcs/tests/test_git.py',
            'vcs/tests/test_changesets.py']),
        ('repository:%s def test' % HG_REPO, 18, [
            'vcs/tests/test_git.py',
            'vcs/tests/test_changesets.py']),
        ('"def main"', 9, [
            'vcs/__init__.py',
            'vcs/tests/__init__.py',
            'vcs/utils/progressbar.py']),
        ('owner:test_admin', 358, [
            'vcs/tests/base.py',
            'MANIFEST.in',
            'vcs/utils/termcolors.py',
            'docs/theme/ADC/static/documentation.png']),
        ('owner:test_admin def main', 72, [
            'vcs/__init__.py',
            'vcs/tests/test_utils_filesize.py',
            'vcs/tests/test_cli.py']),
        ('owner:michał test', 0, []),
    ])
    def test_search_files(self, query, expected_hits, expected_paths):
        self.log_user()
        response = self.app.get(route_path('search'),
            {'q': query, 'type': 'content', 'page_limit': 500})

        response.mustcontain('%s results' % expected_hits)
        for path in expected_paths:
            response.mustcontain(path)

    @pytest.mark.parametrize("query, expected_hits, expected_commits", [
        ('bother to ask where to fetch repo during tests', 3, [
            ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1'),
            ('git', 'c6eb379775c578a95dad8ddab53f963b80894850'),
            ('svn', '98')]),
        ('michał', 0, []),
        ('changed:tests/utils.py', 36, [
            ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1')]),
        ('changed:vcs/utils/archivers.py', 11, [
            ('hg', '25213a5fbb048dff8ba65d21e466a835536e5b70'),
            ('hg', '47aedd538bf616eedcb0e7d630ea476df0e159c7'),
            ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
            ('hg', '04ad456aefd6461aea24f90b63954b6b1ce07b3e'),
            ('git', 'c994f0de03b2a0aa848a04fc2c0d7e737dba31fc'),
            ('git', 'd1f898326327e20524fe22417c22d71064fe54a1'),
            ('git', 'fe568b4081755c12abf6ba673ba777fc02a415f3'),
            ('git', 'bafe786f0d8c2ff7da5c1dcfcfa577de0b5e92f1')]),
        ('added:README.rst', 3, [
            ('hg', '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'),
            ('git', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
            ('svn', '8')]),
        ('changed:lazy.py', 15, [
            ('hg', 'eaa291c5e6ae6126a203059de9854ccf7b5baa12'),
            ('git', '17438a11f72b93f56d0e08e7d1fa79a378578a82'),
            ('svn', '82'),
            ('svn', '262'),
            ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
            ('git', '33fa3223355104431402a888fa77a4e9956feb3e')
            ]),
        ('author:marcin@python-blog.com '
         'commit_id:b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
             ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
        ('b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
             ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
        ('b986218b', 1, [
             ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
    ])
    def test_search_commit_messages(
            self, query, expected_hits, expected_commits, enabled_backends):
        self.log_user()
        response = self.app.get(route_path('search'),
            {'q': query, 'type': 'commit', 'page_limit': 500})

        response.mustcontain('%s results' % expected_hits)
        for backend, commit_id in expected_commits:
            if backend in enabled_backends:
                response.mustcontain(commit_id)

    @pytest.mark.parametrize("query, expected_hits, expected_paths", [
        ('readme.rst', 3, []),
        ('test*', 75, []),
        ('*model*', 1, []),
        ('extension:rst', 48, []),
        ('extension:rst api', 24, []),
    ])
    def test_search_file_paths(self, query, expected_hits, expected_paths):
        self.log_user()
        response = self.app.get(route_path('search'),
            {'q': query, 'type': 'path', 'page_limit': 500})

        response.mustcontain('%s results' % expected_hits)
        for path in expected_paths:
            response.mustcontain(path)

    def test_search_commit_message_specific_repo(self, backend):
        self.log_user()
        response = self.app.get(
            route_path('search_repo',repo_name=backend.repo_name),
            {'q': 'bother to ask where to fetch repo during tests',
             'type': 'commit'})

        response.mustcontain('1 results')

    def test_filters_are_not_applied_for_admin_user(self):
        self.log_user()
        with mock.patch('whoosh.searching.Searcher.search') as search_mock:

            self.app.get(route_path('search'),
                {'q': 'test query', 'type': 'commit'})
        assert search_mock.call_count == 1
        _, kwargs = search_mock.call_args
        assert kwargs['filter'] is None

    def test_filters_are_applied_for_normal_user(self, enabled_backends):
        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
        with mock.patch('whoosh.searching.Searcher.search') as search_mock:
            self.app.get(route_path('search'),
                {'q': 'test query', 'type': 'commit'})
        assert search_mock.call_count == 1
        _, kwargs = search_mock.call_args
        assert isinstance(kwargs['filter'], query.Or)
        expected_repositories = [
            f'vcs_test_{b}' for b in enabled_backends]
        queried_repositories = [
            name for type_, name in kwargs['filter'].all_terms()]
        for repository in expected_repositories:
            assert repository in queried_repositories
