# -*- coding: utf-8 -*-
import json
import logging
import couchdb
import datetime
from openprocurement.auction.databridge import ResourceFeeder
from gevent import spawn
from openprocurement.auction import core as core_module
from openprocurement.auction.chronograph import AuctionsChronograph
from openprocurement.auction.databridge import AuctionsDataBridge
from openprocurement.auction.helpers.chronograph import \
    MIN_AUCTION_START_TIME_RESERV
from openprocurement.auction.tests.utils import get_tenders_dummy
from openprocurement.auction.worker.auction import Auction
from openprocurement.auction.tests.utils import update_auctionPeriod, \
    AUCTION_DATA
from openprocurement.auction.tests.utils import worker_defaults, \
    test_chronograph_config, worker_defaults_file_path, test_bridge_config
import yaml
import openprocurement.auction.helpers.couch as couch_module
import openprocurement.auction.chronograph as chrono_module
from openprocurement.auction.tests.utils import DummyTrue, iterview_wrappper
import pytest
from webtest import TestApp
from openprocurement.auction.auctions_server import auctions_server as frontend
import openprocurement.auction.auctions_server as auctions_server_module
from mock import MagicMock, NonCallableMock
from couchdb import Server
from memoize import Memoizer
from mock import sentinel
from sse import Sse as PySse
from flask import Response


DEFAULT = sentinel.DEFAULT
RESPONSE = sentinel.response

LOGGER = logging.getLogger('Log For Tests')

test_log_config = {
     'version': 1,
     'disable_existing_loggers': False,
     'formatters': {'simpleFormatter': {'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'}},
     'handlers': {'journal': {'class': 'ExtendedJournalHandler.ExtendedJournalHandler', 'formatter': 'simpleFormatter', 'SYSLOG_IDENTIFIER': 'AUCTIONS_LOG_FOR_TESTS', 'level': 'DEBUG'}},
     'loggers': {'Log For Tests': {'handlers': ['journal'], 'propagate': False, 'level': 'DEBUG'},
                 '': {'handlers': ['journal'], 'propagate': False, 'level': 'DEBUG'}}
     }

logging.config.dictConfig(test_log_config)


@pytest.fixture(scope='function')
def db(request):
    server = couchdb.Server("http://" + worker_defaults['COUCH_DATABASE'].split('/')[2])
    name = worker_defaults['COUCH_DATABASE'].split('/')[3]

    documents = getattr(request, 'param', None)

    def delete():
        del server[name]

    if name in server:
        delete()

    data_base = server.create(name)

    if documents:
        for doc in documents:
            data_base.save(doc)
            
    request.addfinalizer(delete)

    return data_base


@pytest.fixture(scope='function')
def chronograph(request, mocker):
    logging.config.dictConfig(test_chronograph_config)

    # We use 'dummy_true' variable instead of real True and mock iterview
    # with iterview_wrapper function to tear down the test gracefully.
    # Without these steps iterview from previous test running continue working
    # while next test have already been launched.
    dummy_true = DummyTrue()
    couch_module.TRUE = dummy_true
    mocker.patch.object(chrono_module, 'iterview',
                        side_effect=iterview_wrappper, autospec=True)

    chrono = AuctionsChronograph(test_chronograph_config)
    chrono_thread = spawn(chrono.run)

    def delete_chronograph():
        chrono.scheduler.execution_stopped = True
        dummy_true.ind = False
        chrono_thread.join(0.15)
        chrono.scheduler.shutdown(True, True)

    request.addfinalizer(delete_chronograph)

    return chrono_thread


@pytest.fixture(scope="function")
def auction(request):
    defaults = {'time': MIN_AUCTION_START_TIME_RESERV,
                'delta_t': datetime.timedelta(seconds=10)}

    params = getattr(request, 'param', defaults)
    for key in defaults.keys():
        params[key] = defaults[key] if params.get(key, 'default') == 'default'\
            else params[key]

    with update_auctionPeriod(
            AUCTION_DATA['simple']['path'],
            auction_type='simple',
            time_shift=params['time']+params['delta_t']) \
            as updated_doc, open(updated_doc, 'r') as auction_updated_data:
        auction_inst = Auction(
            tender_id=AUCTION_DATA['simple']['data']['data']['tenderID'],
            worker_defaults=yaml.load(open(worker_defaults_file_path)),
            auction_data=json.load(auction_updated_data),
            lot_id=False)

    return auction_inst


@pytest.fixture(scope='function')
def bridge(request, mocker):
    params = getattr(request, 'param', {})
    tenders = params.get('tenders', [])
    bridge_config = params.get('bridge_config', test_bridge_config)

    mock_resource_items = \
        mocker.patch.object(ResourceFeeder, 'get_resource_items',
                            side_effect=get_tenders_dummy(tenders),
                            autospec=True)

    mock_do_until_success = \
        mocker.patch.object(core_module, 'do_until_success', autospec=True)

    bridge_inst = AuctionsDataBridge(bridge_config)
    thread = spawn(bridge_inst.run)

    return {'bridge': bridge_inst,
            'bridge_thread': thread,
            'bridge_config': bridge_config,
            'tenders': tenders,
            'mock_resource_items': mock_resource_items,
            'mock_do_until_success': mock_do_until_success}


@pytest.fixture(scope='function')
def send(mocker):
    mock_send = mocker.patch.object(auctions_server_module, 'send')
    return mock_send


@pytest.fixture(scope='function')
def response(mocker):
    mock_response = mocker.patch.object(auctions_server_module, 'Response',
                                        return_value='Response Message')
    return mock_response


@pytest.fixture(scope='function')
def patch_response(request, mocker):
    params = getattr(request, 'param', {})
    resp = params.get('response', DEFAULT)
    mock_response = mocker.patch.object(auctions_server_module, 'Response',
                                        return_value=resp)
    return {'patch_resp': mock_response, 'result': resp} 


@pytest.fixture(scope='function')
def mock_auctions_server(request, mocker):
    params = getattr(request, 'param', {})

    server_config_redis = params.get('server_config_redis', DEFAULT)
    connection_limit = params.get('connection_limit', DEFAULT)
    get_mapping = params.get('get_mapping', DEFAULT)
    proxy_path = params.get('proxy_path', DEFAULT)
    event_sources_pool = params.get('event_sources_pool', DEFAULT)
    proxy_connection_pool = params.get('proxy_connection_pool', DEFAULT)
    stream_proxy = params.get('stream_proxy', DEFAULT)
    db = params.get('db', DEFAULT)
    request_headers = params.get('request_headers', [])
    request_url = params.get('request_url', DEFAULT)
    redirect_url = params.get('redirect_url', DEFAULT)
    abort = params.get('abort', DEFAULT)

    class AuctionsServerAttributesContainer(object):
        logger = NotImplemented
        proxy_mappings = NotImplemented
        config = NotImplemented
        event_sources_pool = NotImplemented
        proxy_connection_pool = NotImplemented
        get_mapping = NotImplemented
        db = NotImplemented
        request_headers = NotImplemented

    class Request(object):
        headers = NotImplemented
        environ = NotImplemented
        url = NotImplemented

    class Config(object):
        __getitem__ = NotImplemented

    def config_getitem(item):
        if item == 'REDIS':
            return server_config_redis
        elif item == 'event_source_connection_limit':
            return connection_limit
        else:
            raise KeyError

    mock_path_info = MagicMock()

    def environ_setitem(item, value):
        if item == 'PATH_INFO':
            mock_path_info(value)
            return value
        else:
            raise KeyError

    mocker.patch.object(auctions_server_module, 'get_mapping', get_mapping)
    patch_pysse = mocker.patch.object(auctions_server_module, 'PySse',
                                      spec_set=PySse)
    patch_add_message = patch_pysse.return_value.add_message

    patch_request = mocker.patch.object(auctions_server_module, 'request',
                                        spec_set=Request)
    patch_request.environ.__setitem__.side_effect = environ_setitem
    patch_request.headers = request_headers
    patch_request.url = request_url

    patch_redirect = mocker.patch.object(auctions_server_module, 'redirect',
                                         return_value=redirect_url)
    patch_abort = mocker.patch.object(auctions_server_module, 'abort',
                                      return_value=abort)

    patch_StreamProxy = \
        mocker.patch.object(auctions_server_module, 'StreamProxy',
                            return_value=stream_proxy)

    auctions_server = NonCallableMock(spec_set=
                                      AuctionsServerAttributesContainer)

    logger = MagicMock(spec_set=frontend.logger)
    proxy_mappings = MagicMock(spec_set=Memoizer({}))
    proxy_mappings.get.return_value = proxy_path
    config = MagicMock(spec_set=Config)
    config.__getitem__.side_effect = config_getitem

    auctions_server.logger = logger
    auctions_server.proxy_mappings = proxy_mappings
    auctions_server.config = config
    auctions_server.event_sources_pool = event_sources_pool
    auctions_server.proxy_connection_pool = proxy_connection_pool
    auctions_server.db = db

    mocker.patch.object(auctions_server_module, 'auctions_server',
                        auctions_server)

    return {'server': auctions_server,
            'proxy_mappings': proxy_mappings,
            'mock_path_info': mock_path_info,
            'patch_StreamProxy': patch_StreamProxy,
            'patch_redirect': patch_redirect,
            'patch_abort': patch_abort,
            'patch_PySse': patch_pysse,
            'patch_add_message': patch_add_message}


@pytest.fixture(scope='function')
def auctions_server(request):
    params = getattr(request, 'param', {})
    server_config = params.get('server_config', {})

    logger = MagicMock(spec_set=frontend.logger)
    logger.name = server_config.get('logger_name', 'some-logger')
    frontend.logger_name = logger.name
    frontend._logger = logger

    for key in ('limit_replications_func', 'limit_replications_progress'):
        frontend.config.pop(key, None)

    for key in ('limit_replications_func', 'limit_replications_progress'):
        if key in server_config:
            frontend.config[key] = server_config[key]

    frontend.couch_server = MagicMock(spec_set=Server)
    frontend.config['TIMEZONE'] = 'some_time_zone'

    if 'couch_tasks' in params:
        frontend.couch_server.tasks.return_value = params['couch_tasks']

    test_app = TestApp(frontend)
    return {'app': frontend, 'test_app': test_app}


@pytest.yield_fixture(scope="function")
def log_for_test(request):
    LOGGER.debug('-------- Test Start ---------')
    LOGGER.debug('Current module: {0}'.format(request.module.__name__))
    LOGGER.debug('Current test class: {0}'.format(request.cls.__name__))
    LOGGER.debug('Current test function: {0}'
                 .format(request.function.__name__))
    yield LOGGER
    LOGGER.debug('-------- Test End ---------')