from __future__ import unicode_literals, division, absolute_import
from builtins import *  # noqa pylint: disable=unused-import, redefined-builtin

import jsonschema
from future.utils import PY2
from future.backports.http import client as backport_client

import re
import os
import sys
import yaml
import logging
import shutil
import requests

import itertools

from contextlib import contextmanager

import mock
import pytest
from path import Path
from vcr import VCR
from vcr.stubs import VCRHTTPSConnection, VCRHTTPConnection

import flexget.logger
from flexget.manager import Manager
from flexget.plugin import load_plugins
from flexget.task import Task, TaskAbort
from flexget.webserver import User
from flexget.manager import Session
from flexget.api import api_app

log = logging.getLogger('tests')

VCR_CASSETTE_DIR = os.path.join(os.path.dirname(__file__), 'cassettes')
VCR_RECORD_MODE = os.environ.get('VCR_RECORD_MODE', 'once')

vcr = VCR(
    cassette_library_dir=VCR_CASSETTE_DIR,
    record_mode=VCR_RECORD_MODE,
    custom_patches=(
        (backport_client, 'HTTPSConnection', VCRHTTPSConnection),
        (backport_client, 'HTTPConnection', VCRHTTPConnection),
    )
)


# --- These are the public fixtures tests can ask for ---

@pytest.fixture(scope='class')
def config(request):
    """
    If used inside a test class, uses the `config` class attribute of the class.
    This is used by `manager` fixture, and can be parametrized.
    """
    return request.cls.config


@pytest.yield_fixture()
def manager(request, config, caplog, monkeypatch, filecopy):  # enforce filecopy is run before manager
    """
    Create a :class:`MockManager` for this test based on `config` argument.
    """
    if 'tmpdir' in request.fixturenames:
        config = config.replace('__tmp__', request.getfuncargvalue('tmpdir').strpath)
    try:
        mockmanager = MockManager(config, request.cls.__name__)
    except Exception:
        # Since we haven't entered the test function yet, pytest won't print the logs on failure. Print them manually.
        print(caplog.text())
        raise
    yield mockmanager
    mockmanager.shutdown()


@pytest.fixture()
def execute_task(manager):
    """
    A function that can be used to execute and return a named task in `config` argument.
    """

    def execute(task_name, abort=False, options=None):
        """
        Use to execute one test task from config.

        :param abort: If `True` expect (and require) this task to abort.
        """
        log.info('********** Running task: %s ********** ' % task_name)
        config = manager.config['tasks'][task_name]
        task = Task(manager, task_name, config=config, options=options)

        try:
            if abort:
                with pytest.raises(TaskAbort):
                    task.execute()
            else:
                task.execute()
        finally:
            try:
                task.session.close()
            except Exception:
                pass
        return task

    return execute


@pytest.yield_fixture()
def use_vcr(request, monkeypatch):
    """
    This fixture is applied automatically to any test using the `online` mark. It will record and playback network
    sessions using VCR.

    The record mode of VCR can be set using the VCR_RECORD_MODE environment variable when running tests.
    """
    if VCR_RECORD_MODE == 'off':
        yield None
    else:
        module = request.module.__name__.split('tests.')[-1]
        class_name = request.cls.__name__
        cassette_name = '.'.join([module, class_name, request.function.__name__])
        cassette_path = os.path.join(VCR_CASSETTE_DIR, cassette_name)
        online = True
        if vcr.record_mode == 'none':
            online = False
        elif vcr.record_mode == 'once':
            online = not os.path.exists(cassette_path)
        # If we are not going online, disable domain limiting during test
        if not online:
            log.debug('Disabling domain limiters during VCR playback.')
            monkeypatch.setattr('flexget.utils.requests.limit_domains', mock.Mock())
        with vcr.use_cassette(path=cassette_path) as cassette:
            yield cassette


@pytest.fixture()
def api_client(manager):
    with Session() as session:
        user = session.query(User).first()
        if not user:
            user = User(name='flexget', password='flexget')
            session.add(user)
            session.commit()
        return APIClient(user.token)


@pytest.fixture()
def schema_match(manager):
    """
    This fixture enables verifying JSON Schema. Return a list of validation error dicts. List is empty if no errors
    occurred.
    """

    def match(schema, response):
        validator = jsonschema.Draft4Validator(schema)
        errors = list(validator.iter_errors(response))
        return [dict(value=list(e.path), message=e.message) for e in errors]

    return match


@pytest.fixture()
def link_headers(manager):
    """
    Parses link headers and return them in dict form
    """

    def headers(response):
        links = {}
        for link in requests.utils.parse_header_links(response.headers.get('link')):
            url = link['url']
            page = int(re.search('(?<!per_)page=(\d)', url).group(1))