import http
import os
import socket
import time

import pytest
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
from pytest_django.live_server_helper import LiveServer

from perms.models import Perm
from users.models import UserInfo

DEFAULT_USER = 'admin'
DEFAULT_PASSWORD = 'nimda'

TEST_LANGS = sorted(('ru', 'en'))
TEST_TIMEZONES = sorted(('UTC', 'Europe/Moscow', 'Europe/Paris', 'America/New_York'))

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
CUR_DIR = os.path.abspath(os.getcwd())
TIMEOUT = 10  # time before timeout exception appears
POLL_TIMEOUT = 0.3  # time execute next poll cycle, for example when waiting element

from django.conf import settings

LOGS_F = os.path.join(getattr(settings, 'LOG_PATH', ''), getattr(settings, 'USER_LOG_FILENAME', ''))


@pytest.fixture(scope='session')
def test_server() -> LiveServer:
    """ Custom fixture for creating live test server """
    addr = socket.gethostbyname(socket.gethostname())
    server = LiveServer(addr)
    try:
        yield server
    finally:
        server.stop()


def find_message_in_log_file(file, message):
    """ Function for finding certain string in log file
    :param file: Log file descriptor
    :param message: message, that needs to be found
    :return:
        Assert True if message is found
        Assert False if message not found
    """
    with open(file, encoding='utf-8') as f:
        if message in f.read():
            return True, message
        else:
            return False, message


@pytest.fixture
def get_url(test_server):
    """ Get url from liveserver

    :param url: Url's name
    :param kwargs: Dictionary with arguments for reverse function
    :return: string with url
    """

    def _get_url(url, kwargs=None):
        return test_server.url + reverse(url, kwargs=kwargs)

    return _get_url


@pytest.fixture
def add_user_with_permissions(django_user_model):
    """ Add user with selected permissions

    :param username: User name
    :param password: User password
    :param permissions: List with Perm's permissions, that will be add to created user
    :param is_superuser: If True - user will be superuser
    :return: created user with updated permissions cache
    """

    def _login_user(username, password, permissions=[], is_superuser=False):
        user = django_user_model.objects.create_user(username=username, password=password)
        if is_superuser:
            user.is_superuser = True
        user.save()

        UserInfo(user=user).save()

        for cur in permissions:
            user.user_permissions.add(Perm.get_rights(cur))

        return django_user_model.objects.get(pk=user.pk)

    return _login_user


def wait_db_element(query_method, message='', timeout=TIMEOUT, poll=POLL_TIMEOUT):
    end_time = time.time() + timeout
    screen, stacktrace = None, None
    while True:
        try:
            e = query_method()
            if e:
                return e
        except ObjectDoesNotExist as exc:
            message = message or str(exc)
            screen = getattr(exc, 'screen', None)
            stacktrace = getattr(exc, 'stacktrace', None)
        time.sleep(poll)
        if time.time() > end_time:
            break
    # TODO: Maybe add here screen and stacktrace from above
    raise RuntimeError(message)


class PermApiBaseTest:
    """ Test class for checking API permissions
    @param get_url: URL to check
    @param test_server: testing server instance
    @param api_list: Fixture with API for testing in following format:
        [Permission_to_check, URL of api, arguments for API (optional)]
        e.x. with arguments
        [[Perm.can_export_events], 'api_change_event_export', dict(state='disable')]
        e.x. without arguments
        [[Perm.can_export_events], 'api_change_event_export']
    """

    def test_api_without_perms(self, get_url, test_server, api_list, add_user_with_permissions, client):
        username = 'user_with_no_perms'
        password = 'nimda'
        user = add_user_with_permissions(username=username, password=password)
        client.force_login(user)
        if len(api_list) == 2:
            response = client.get(get_url(api_list[1]))
        elif len(api_list) == 3:
            response = client.get(get_url(api_list[1], api_list[2]))
        else:
            assert False, f'Incorrect format of api_list instance: {api_list}'
        assert response.status_code == http.HTTPStatus.FORBIDDEN

    def test_api_with_perms(self, get_url, test_server, add_user_with_permissions, api_list, client):
        # username 'admin_1' used instead of DEFAULT_USER because in some cases login_user failing due to the
        # existence of user with that username
        # Fixed by @lvlukianenko in recent commits
        username = 'user_with_perms'
        password = 'nimda'
        user = add_user_with_permissions(username=username, password=password, permissions=api_list[0])
        client.force_login(user)
        if len(api_list) == 2:
            response = client.get(get_url(api_list[1]))
        elif len(api_list) == 3:
            response = client.get(get_url(api_list[1], api_list[2]))
        else:
            assert False, f'Incorrect format of api_list instance: {api_list}'
        assert response.status_code == http.HTTPStatus.OK

# TODO: Move to integration tests
# class TestDeleteElasticsearchIndexes:
#     index_name = 'test-index'
#
#     @pytest.fixture(autouse=True)
#     def setup_tests(self):
#         self.es = Elasticsearch([{'host': ELK_HOST, 'port': ELK_PORT}], http_auth=(ELK_LOGIN, ELK_PASS))
#         for i in range(3):
#             self.es.indices.create(index=f'{self.index_name}-{i}', ignore=400)
#         yield
#         self.es.indices.delete(index=f'{self.index_name}-*', ignore=[400, 404])
#
#     def test_delete_all_index_by_template(self):
#         index_template = f'{self.index_name}-*'
#         index_count = len(self.es.indices.get_alias(index=index_template).keys())
#         assert index_count == 3
#         delete_elasticsearch_indexes_by_template(index_template)
#         assert len(self.es.indices.get_alias(index=index_template)) == 0
#
#     @pytest.mark.parametrize('exclude_indexes,expected', TEST_EXCLUDE_INDEXES)
#     def test_delete_index_by_template_witch_exclude_index(self, exclude_indexes: set, expected: int):
#         index_template = f'{self.index_name}-*'
#         delete_elasticsearch_indexes_by_template(index_template, es=self.es, exclude_indexes=exclude_indexes)
#         assert len(self.es.indices.get_alias(index=index_template, ignore=[400, 404])) == expected
