import copy
import logging
import os
from os.path import dirname as _dir
import time
import pytest

from tests.functional.utils.docker_utils import create_docker_client
from tests.functional.utils.http_utils import http_get, http_post, http_del, http_put, \
    RequestFailedError, get_api_conf

FT_ACCOUNT = 'functional_test'
DELETE_ACCOUNT_TIMEOUT_SEC = 60*5


def get_logger(name):
    return logging.getLogger('conftest.%s' % name)


def pytest_sessionstart(session):
    BASE_FORMAT = "[%(name)s][%(levelname)-6s] %(message)s"
    FILE_FORMAT = "[%(asctime)s]" + BASE_FORMAT

    root_logger = logging.getLogger('conftest')
    dir_path = os.path.dirname(os.path.realpath(__file__))
    top_level = _dir(_dir(dir_path))
    log_file = os.path.join(top_level, 'pytest-functional-tests.log')

    root_logger.setLevel(logging.DEBUG)

    # File Logger
    fh = logging.FileHandler(log_file)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(logging.Formatter(FILE_FORMAT, "%Y-%m-%d %H:%M:%S"))

    root_logger.addHandler(fh)


def get_ft_user():
    return {
        'username': os.environ.get('ANCHORE_FUNCTIONAL_TEST__ACCOUNT_USER', 'ft_user'),
        'password': os.environ.get('ANCHORE_FUNCTIONAL_TEST_ACCOUNT_PASSWORD', 'f00b4r')
    }


def get_ft_user_api_conf():
    """
    Get a non-root admin user for the functional_test account, that can be used to configure API calls.
    Note: In order to be compatible with http_utils methods, this needs to return an object like
    DEFAULT_API_CONF, ex:
        {
            'ANCHORE_API_USER': '<username>',
            'ANCHORE_API_PASS': '<password>',
            'ANCHORE_BASE_URL': '<base_url>',
            'ANCHORE_API_ACCOUNT': '<account_name>'
        }

    This function translates what is generated by the session-scoped fixture below:
    create_functional_test_account_with_teardown
    """

    # Don't need to do deep copy because api_conf should be single level
    api_conf = copy.copy(get_api_conf())
    ft_user = get_ft_user()

    # override fields (except for base_url)
    api_conf['ANCHORE_API_USER'] = ft_user['username']
    api_conf['ANCHORE_API_PASS'] = ft_user['password']
    api_conf['ANCHORE_API_ACCOUNT'] = FT_ACCOUNT

    return api_conf


def does_ft_account_exist():
    ft_account_resp = http_get(['accounts', FT_ACCOUNT])
    return ft_account_resp.code != 404


@pytest.fixture(scope="session", autouse=True)
def create_functional_test_account_with_teardown(request):
    _logger = logging.getLogger('conftest')
    """
    This fixture implicitly tests get_by_account_name, create, update state, and delete operations, but essentially,
    creates a functional_test account with a user ('ft_user' unless overridden by environment variables), and then
    deletes this account (blocking until deletion is complete) at the end of the test session
    """
    def disable_and_delete_functional_test_account():
        """
        This method wil dynamically, and in a blocking fashion, handle account deletion, which requires that the
        functional_test account be disabled before deletion. If the functional_test account is currently enabled, it
        will disable and then delete the account, waiting for the deletion to complete. If the functional_test account
        is already disabled, it will delete the account,  and wait for the deletion to complete. If the functional_test
        account is currently awaiting deletion, it will wait for the deletion to complete. If the functional_test
        account is not found, it will exit.
        """
        def await_account_deletion():
            """
            This method is helpful for awaiting account deletion of the functional_test account, with a timeout governed
            by DELETE_ACCOUNT_TIMEOUT_SEC. It awaits in 5 second intervals.
            """
            start_time_sec = time.time()
            result = 200
            while result != 404:
                time.sleep(5)
                ft_get_account_resp = http_get(['accounts', FT_ACCOUNT])
                _logger.info("Waiting for functional_test account to fully delete. Time Elapsed={}sec"
                             .format(int(time.time() - start_time_sec)))
                if not (ft_get_account_resp.code == 200 or ft_get_account_resp.code == 404):
                    _logger.error(ft_get_account_resp)
                    raise RequestFailedError(ft_get_account_resp.url,
                                             ft_get_account_resp.code,
                                             ft_get_account_resp.body)
                if time.time() - start_time_sec >= DELETE_ACCOUNT_TIMEOUT_SEC:
                    raise TimeoutError('Timed out waiting for functional_test account to delete')

                result = ft_get_account_resp.code

        ft_account_resp = http_get(['accounts', FT_ACCOUNT])

        if ft_account_resp.code == 404:
            _logger.info('functional_test account not found')
            return

        state = ft_account_resp.body.get('state')
        if state == 'enabled':
            _logger.info('functional_test account found, and enabled. Disabling')
            disable_account_resp = http_put(['accounts', FT_ACCOUNT, 'state'], {'state': 'disabled'})
            if disable_account_resp.code != 200:
                raise RequestFailedError(disable_account_resp.url, disable_account_resp.code, disable_account_resp.body)
        elif state == 'deleting':
            _logger.info('functional_test account found, but is currently being deleted')
            await_account_deletion()
            return

        _logger.info('Deleting functional_test account')
        delete_resp = http_del(['accounts', FT_ACCOUNT])
        if not (delete_resp.code == 200 or delete_resp.code == 404):
            raise RequestFailedError(delete_resp.url, delete_resp.code, delete_resp.body)
        await_account_deletion()

    # Delete the account if it exists already for some reason (sanity check)
    disable_and_delete_functional_test_account()
    _logger.info("Creating functional_test account")
    create_resp = http_post(['accounts'], {'name': FT_ACCOUNT, 'email': 'admin@anchore.com'})
    if create_resp.code != 200:
        raise RequestFailedError(create_resp.url, create_resp.code, create_resp.body)

    ft_user = get_ft_user()
    _logger.info("Creating functional_test user: {}".format(ft_user['username']))
    create_user_resp = http_post(['accounts', FT_ACCOUNT, 'users'], ft_user)
    if create_user_resp.code != 200:
        raise RequestFailedError(create_user_resp.url, create_user_resp.code, create_user_resp.body)

    # # Need to reach out to rbac-manager to add the user to the full-control role
    # _logger.info("Giving user full control permissions of account")
    #
    # def get_rbac_api_conf():
    #     rbac_api_conf = copy.copy(get_api_conf())
    #     rbac_base_url = 'http://localhost:8229' if not os.getenv('ANCHORE_TEST_RBAC_BASE_URL') \
    #         else os.getenv('ANCHORE_TEST_RBAC_BASE_URL')
    #     rbac_api_conf['ANCHORE_BASE_URL'] = rbac_base_url
    #     return rbac_api_conf
    #
    # rbac_resp = http_post(['roles', 'full-control', 'members'],
    #                       {'username': ft_user['username'], 'for_account': FT_ACCOUNT},
    #                       config=get_rbac_api_conf)
    # if rbac_resp.code != 200:
    #     raise RequestFailedError(rbac_resp.url, rbac_resp.code, rbac_resp.body)

    request.addfinalizer(disable_and_delete_functional_test_account)
    return ft_user


@pytest.fixture(scope="session")
def docker_client():
    return create_docker_client()
