import os
import random

import aiohttp
from itertools import *

import faker
from faker.providers import internet
from faker.providers import lorem
from faker.providers import misc

from src.settings import config
from src.server import app
from src.server.validation import is_valid_base_comment

from test.testcase import AsyncioTestCase


if 'slack_webhook' in config:
    config.pop('slack_webhook')


fake = faker.Faker()
fake.add_provider(internet)
fake.add_provider(lorem)
fake.add_provider(misc)


def fake_lbryusername() -> str:
    return '@' + fake.user_name()


def nothing():
    pass


def fake_signature() -> str:
    return fake.sha256() + fake.sha256()


def fake_signing_ts() -> str:
    return str(random.randint(1, 2**32 - 1))


async def jsonrpc_post(url, method, **params):
    json_body = {
        'jsonrpc': '2.0',
        'id': None,
        'method': method,
        'params': params
    }
    async with aiohttp.request('POST', url, json=json_body) as request:
        return await request.json()


replace = {
    'claim_id': fake.sha1,
    'comment': fake.text,
    'channel_id': fake.sha1,
    'channel_name': fake_lbryusername,
    'signature': fake_signature,
    'signing_ts': fake_signing_ts,
    'parent_id': fake.sha256,
}


def create_test_comments(values: iter, **default):
    vars_combo = chain.from_iterable(combinations(values, r) for r in range(1, len(values) + 1))
    return [{k: replace[k]() if k in comb else v for k, v in default.items()}
            for comb in vars_combo]


class ServerTest(AsyncioTestCase):
    db_file = 'test.db'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.host = 'localhost'
        self.port = 5931

    @property
    def url(self):
        return f'http://{self.host}:{self.port}/api'

    @classmethod
    def tearDownClass(cls) -> None:
        print('exit reached')
        os.remove(cls.db_file)

    async def asyncSetUp(self):
        await super().asyncSetUp()
        self.server = app.CommentDaemon(config, db_file=self.db_file)
        await self.server.start(host=self.host, port=self.port)
        self.addCleanup(self.server.stop)

    async def post_comment(self, **params):
        return await jsonrpc_post(self.url, 'create_comment', **params)

    async def test01CreateCommentNoReply(self):
        anonymous_test = create_test_comments(
            ('claim_id', 'channel_id', 'channel_name', 'comment'),
            comment=None,
            channel_name=None,
            channel_id=None,
            claim_id=None
        )
        for test in anonymous_test:
            with self.subTest(test='null fields: ' + ', '.join(k for k, v in test.items() if not v)):
                message = await self.post_comment(**test)
                self.assertTrue('result' in message or 'error' in message)
                if 'error' in message:
                    self.assertFalse(is_valid_base_comment(**test))
                else:
                    self.assertTrue(is_valid_base_comment(**test))

    async def test02CreateNamedCommentsNoReply(self):
        named_test = create_test_comments(
            ('channel_name', 'channel_id', 'signature'),
            claim_id='1234567890123456789012345678901234567890',
            channel_name='@JamieFoxx',
            channel_id='1234567890'*4,
            comment='blah blah blahbity blah',
            signature=None
        )
        for test in named_test:
            with self.subTest(test=test):
                message = await self.post_comment(**test)
                self.assertTrue('result' in message or 'error' in message)
                if 'error' in message:
                    self.assertFalse(is_valid_base_comment(**test))
                else:
                    self.assertTrue(is_valid_base_comment(**test))

    async def test03CreateAllTestComments(self):
        test_all = create_test_comments(replace.keys(), **{
            k: None for k in replace.keys()
        })
        for test in test_all:
            with self.subTest(test=test):
                message = await self.post_comment(**test)
                self.assertTrue('result' in message or 'error' in message)
                if 'error' in message:
                    self.assertFalse(is_valid_base_comment(**test))
                else:
                    self.assertTrue(is_valid_base_comment(**test))

    async def test04CreateAllReplies(self):
        claim_id = '1d8a5cc39ca02e55782d619e67131c0a20843be8'
        parent_comment = await self.post_comment(
            channel_name='@KevinWalterRabie',
            channel_id=fake.sha1(),
            comment='Hello everybody and welcome back to my chan nel',
            claim_id=claim_id,
            signing_ts='1234',
            signature='_'*128
        )
        parent_id = parent_comment['result']['comment_id']
        test_all = create_test_comments(
            ('comment', 'channel_name', 'channel_id', 'signature', 'parent_id'),
            parent_id=parent_id,
            comment=None,
            channel_name=None,
            channel_id=None,
            signature=None,
            claim_id=claim_id
        )
        for test in test_all:
            with self.subTest(test=test):
                if test['parent_id'] != parent_id:
                    continue
                else:
                    message = await self.post_comment(**test)
                    self.assertTrue('result' in message or 'error' in message)
                    if 'error' in message:
                        self.assertFalse(is_valid_base_comment(**test))
                    else:
                        self.assertTrue(is_valid_base_comment(**test))

    async def testSlackWebhook(self):
        claim_id = '1d8a5cc39ca02e55782d619e67131c0a20843be8'
        channel_name = '@name'
        channel_id = fake.sha1()
        signature = '{}'*64
        signing_ts = '1234'

        base = await self.post_comment(
            channel_name=channel_name,
            channel_id=channel_id,
            comment='duplicate',
            claim_id=claim_id,
            signing_ts=signing_ts,
            signature=signature
        )

        comment_id = base['result']['comment_id']

        with self.subTest(test=comment_id):
            await self.post_comment(
                channel_name=channel_name,
                channel_id=channel_id,
                comment='duplicate',
                claim_id=claim_id,
                signing_ts=signing_ts,
                signature=signature
            )


class ListCommentsTest(AsyncioTestCase):
    replace = {
        'claim_id': fake.sha1,
        'comment': fake.text,
        'channel_id': fake.sha1,
        'channel_name': fake_lbryusername,
        'signature': fake_signature,
        'signing_ts': fake_signing_ts,
        'parent_id': nothing
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.host = 'localhost'
        self.port = 5931
        self.db_file = 'list_test.db'
        self.claim_id = '1d8a5cc39ca02e55782d619e67131c0a20843be8'
        self.comment_ids = None

    @property
    def url(self):
        return f'http://{self.host}:{self.port}/api'

    async def post_comment(self, **params):
        return await jsonrpc_post(self.url, 'create_comment', **params)

    def tearDown(self) -> None:
        print('exit reached')
        os.remove(self.db_file)

    async def create_lots_of_comments(self, n=23):
        self.comment_list = [{key: self.replace[key]() for key in self.replace.keys()} for _ in range(23)]
        for comment in self.comment_list:
            comment['claim_id'] = self.claim_id
        self.comment_ids = [(await self.post_comment(**comm))['result']['comment_id']
                            for comm in self.comment_list]

    async def asyncSetUp(self):
        await super().asyncSetUp()
        self.server = app.CommentDaemon(config, db_file=self.db_file)
        await self.server.start(self.host, self.port)
        self.addCleanup(self.server.stop)

    async def testListComments(self):
        await self.create_lots_of_comments()
        response_one = await jsonrpc_post(
            self.url, 'get_claim_comments', page_size=20, page=1, top_level=1, claim_id=self.claim_id
        )
        self.assertIsNotNone(response_one)
        self.assertIn('result', response_one)
        response_one: dict = response_one['result']
        self.assertEqual(response_one['page_size'], len(response_one['items']))
        self.assertIn('items', response_one)

        comments = response_one['items']
        hidden = list(filter(lambda c: c['is_hidden'], comments))
        self.assertEqual(hidden, [])

        self.assertGreaterEqual(response_one['total_pages'], response_one['page'])
        last_page = response_one['total_pages']
        response = await jsonrpc_post(self.url, 'get_claim_comments', page_size=20,
                                page=last_page, top_level=1, claim_id=self.claim_id)
        self.assertIsNotNone(response)
        self.assertIn('result', response)
        response: dict = response['result']
        self.assertIs(type(response['items']), list)
        self.assertEqual(response['total_items'], response_one['total_items'])
        self.assertEqual(response['total_pages'], response_one['total_pages'])