import datetime as dt
import functools
import re
from functools import partial
from unittest import TestCase
from unittest.mock import patch

import responses
from aioresponses import aioresponses, CallbackResult

from dimensigon.__main__ import join, Gate, token as generate_token, Locker, User
from dimensigon.domain.entities import Server, Dimension, Catalog
from dimensigon.utils.helpers import generate_dimension
from dimensigon.web import create_app, db


class TestApi(TestCase):

    @patch('dimensigon.domain.entities.get_now')
    def setUp(self, mock_now):
        """Create and configure a new app instance for each test."""
        mock_now.return_value = dt.datetime(2019, 4, 1, tzinfo=dt.timezone.utc)
        # create the app with common test config
        self.app_join = create_app('test')
        self.app_join.config['SECURIZER'] = True
        self.app_join.config['SERVER_NAME'] = 'new'
        with self.app_join.app_context():
            db.create_all()
            Locker.set_initial()
            User.set_initial()
            Server.set_initial()
            me = Server.get_current()
            # remove Gates
            [db.session.delete(g) for g in me.gates]
            g = Gate(me, port=8000, dns='new')
            db.session.add(g)
            db.session.commit()
            self.server_new = Server.get_current().to_json()

        self.app = create_app('test')
        self.app.config['SERVER_NAME'] = 'node1'
        self.app.config['SECURIZER'] = True
        mock_now.return_value = dt.datetime(2019, 3, 1, tzinfo=dt.timezone.utc)
        with self.app.app_context():
            db.create_all()
            Locker.set_initial()
            User.set_initial()
            Server.set_initial()
            me = Server.get_current()
            # remove Gates
            [db.session.delete(g) for g in me.gates]
            g = Gate(me, port=8000, dns='node1')
            db.session.add(g)
            db.session.commit()
            dim = generate_dimension('test')
            dim.current = True
            db.session.add(dim)
            db.session.commit()
            self.dimension = dim.to_json()
            self.server_node1 = Server.get_current().to_json()

        self.client = self.app.test_client()

    def tearDown(self) -> None:
        with self.app_join.app_context():
            db.session.remove()
            db.drop_all()
        with self.app.app_context():
            db.session.remove()
            db.drop_all()

    @patch('dimensigon.domain.entities.route.check_host')
    @patch('dimensigon.domain.entities.get_now')
    # @patch('dimensigon.rsa.newkeys')
    @aioresponses()
    def test_join_command(self, mock_now, mock_check, m):
        mock_now.return_value = dt.datetime(2019, 5, 1, tzinfo=dt.timezone.utc)

        mock_check.return_value = True

        def callback_client(method, client, url, **kwargs):
            kwargs.pop('allow_redirects')

            func = getattr(client, method.lower())
            r = func(url.path, headers=kwargs['headers'], json=kwargs['json'])

            return CallbackResult(method.upper(), status=r.status_code, body=r.data, content_type=r.content_type,
                                  headers=r.headers)

        m.post(re.compile('^https?://node1.*'),
               callback=functools.partial(callback_client, 'POST', self.client), repeat=True)

        def request_callback(request, client):
            method_func = getattr(client, request.method.lower())
            resp = method_func(request.path_url, data=request.body, headers=dict(request.headers))

            return resp.status_code, resp.headers, resp.data

        with responses.RequestsMock() as rsps:
            rsps.add_callback(responses.POST,
                              re.compile('^https?://node1.*'),
                              callback=partial(request_callback, client=self.client))
            rsps.add_callback(responses.GET,
                              re.compile('^https?://node1.*'),
                              callback=partial(request_callback, client=self.client))

            with self.app.app_context():
                runner = self.app.test_cli_runner()
                result = runner.invoke(generate_token, [], catch_exceptions=False)
                token = result.stdout.strip()

            runner = self.app_join.test_cli_runner()

            result = runner.invoke(join, ['--no-ssl', 'node1:5000', token], catch_exceptions=False)

        with self.app.app_context():
            # check if new server created
            server_new = Server.query.get(self.server_new.get('id'))
            self.assertEqual(server_new.last_modified_at, dt.datetime(2019, 5, 1, tzinfo=dt.timezone.utc))

        with self.app_join.app_context():
            self.assertDictEqual(self.dimension, Dimension.get_current().to_json())
            s = Server.query.get(self.server_node1.get('id'))
            self.assertEqual(0, s.route.cost)
            self.assertListEqual([('Gate',), ('Server',), ('User',)],
                                 db.session.query(Catalog.entity).order_by('entity').all())
            self.assertEqual(dt.datetime(2019, 5, 1, tzinfo=dt.timezone.utc),
                             Catalog.query.filter_by(entity='Server').one().last_modified_at)
