import hashlib
from unittest import TestCase
import uuid

from falcon import request
from stoplight import validate
from stoplight.exceptions import ValidationFailed

from deuce.transport import validation as v
from deuce.transport.wsgi import errors


class MockRequest(object):
    pass


class InvalidSeparatorError(Exception):
    """Invalid Separator Error is raised whenever
    a invalid separator is set for joining query strings
    in a url"""

    def __init__(self, msg):
        Exception.__init__(self, msg)


class TestRulesBase(TestCase):

    @staticmethod
    def build_request(params=None, separator='&'):
        """Build a request object to use for testing

        :param params: list of tuples containing the name and value pairs
                       for parameters to add to the QUERY_STRING
        """
        mock_env = {
            'wsgi.errors': 'mock',
            'wsgi.input': 'mock',
            'REQUEST_METHOD': 'PUT',
            'PATH_INFO': '/',
            'SERVER_NAME': 'mock',
            'SERVER_PORT': '8888',
            'QUERY_STRING': None
        }

        if params is not None:
            for param in params:
                name = param[0]
                value = param[1]

                param_set = '{0}='.format(name)
                if value is not None and len(value):
                    param_set = '{0}={1}'.format(name, value)

                if mock_env['QUERY_STRING'] is None:
                    mock_env['QUERY_STRING'] = param_set
                else:
                    if separator in ('&', ';'):
                        mock_env['QUERY_STRING'] = '{1}{0}{2}'.format(
                            separator, mock_env['QUERY_STRING'], param_set)
                    else:
                        raise InvalidSeparatorError('separator in query string'
                                                    'must be & or ;')

        if mock_env['QUERY_STRING'] is None:
            del mock_env['QUERY_STRING']

        return request.Request(mock_env)

    def cases_with_none_okay(self):

        positive_cases = self.__class__.positive_cases[:]
        positive_cases.append(None)

        negative_cases = self.__class__.negative_cases[:]
        while negative_cases.count(None):
            negative_cases.remove(None)
        while negative_cases.count(''):
            negative_cases.remove('')

        return (positive_cases, negative_cases)


class TestRequests(TestRulesBase):

    def test_request(self):

        positive_case = [TestRulesBase.build_request()]

        negative_case = [MockRequest()]

        for case in positive_case:
            v.is_request(case)

        for case in negative_case:
            with self.assertRaises(ValidationFailed):
                v.is_request(none_ok=True)(case)


class TestVaultRules(TestRulesBase):

    positive_cases = [
        'a',
        '0',
        '__vault_id____',
        '-_-_-_-_-_-_-_-',
        'snake_case_is_ok',
        'So-are-hyphonated-names',
        'a' * v.VAULT_ID_MAX_LEN
    ]

    negative_cases = [
        '',  # empty case should raise
        '.', '!', '@', '#', '$', '%',
        '^', '&', '*', '[', ']', '/',
        '@#$@#$@#^@%$@#@#@#$@!!!@$@$@',
        '\\', 'a' * (v.VAULT_ID_MAX_LEN + 1),
        None
    ]

    @validate(vault_id=v.VaultGetRule)
    def utilize_get_vault_id(self, vault_id):
        return True

    @validate(vault_id=v.VaultPutRule)
    def utilize_put_vault_id(self, vault_id):
        return True

    @validate(req=v.RequestRule(v.VaultMarkerRule))
    def utilize_request(self, req, raiseme=False):
        if raiseme:
            raise RuntimeError('QUERY_STRING: {0}'.format(req.query_string))
        else:
            return True

    def test_vault_id(self):

        for name in self.__class__.positive_cases:
            v.val_vault_id(name)

        for name in self.__class__.negative_cases:
            with self.assertRaises(ValidationFailed):
                v.val_vault_id()(name)

    def test_vault_get(self):

        for p_case in self.__class__.positive_cases:
            self.assertTrue(self.utilize_get_vault_id(p_case))

        for case in self.__class__.negative_cases:
            with self.assertRaises(errors.HTTPNotFound):
                self.utilize_get_vault_id(case)

    def test_vault_put(self):

        for p_case in self.__class__.positive_cases:
            self.assertTrue(self.utilize_put_vault_id(p_case))

        for case in self.__class__.negative_cases:
            with self.assertRaises(errors.HTTPBadRequestAPI):
                self.utilize_put_vault_id(case)

    def test_vault_id_marker(self):

        positive_cases, negative_cases = self.cases_with_none_okay()

        for vault_id in positive_cases:
            vault_id_req = TestRulesBase.build_request(params=[('marker',
                                                                vault_id)])
            self.assertTrue(self.utilize_request(vault_id_req))

        # We currently skip the negative test for the VaultMarkerRule
        # due to the nature of the negative cases for the Vault Name.
        # Leaving the code in below should we figure out a good way to
        # capture the data for the URL encoding.
        #
        # Note: It is not a failure of build_request()'s QUERY_STRING building
        #   but a miss-match between it, urllib.parse.urlencode(), and Falcon.
        #   Use of urllib.parse.urlencode() has other issues here as well.
        #
        # for vault_id in negative_cases:
        #    vault_id_req = TestRulesBase.build_request(params=[('marker',
        #                                                        vault_id)])
        #    with self.assertRaises(errors.HTTPNotFound):
        #        self.utilize_request(vault_id_req, raiseme=True)


class TestMetadataBlockRules(TestRulesBase):

    positive_cases = [
        'da39a3ee5e6b4b0d3255bfef95601890afd80709',
        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
        'ffffffffffffffffffffffffffffffffffffffff',
        'a' * 40,
    ]

    negative_cases = [
        '',
        '.',
        'a', '0', 'f', 'F', 'z', '#', '$', '?',
        'a39a3ee5e6b4b0d3255bfef95601890afd80709',  # one char short
        'da39a3ee5e6b4b0d3255bfef95601890afd80709a',  # one char long
        'DA39A3EE5E6B4B0D3255BFEF95601890AFD80709',
        'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
        'AaaAaaAaaaaAaAaaaAaaaaaaaAAAAaaaaAaaaaaa' * 2,
        'AaaAaaAaaaaAaAaaaAaaaaaaaAAAAaaaaAaaaaaa' * 3,
        'AaaAaaAaaaaAaAaaaAaaaaaaaAAAAaaaaAaaaaaa' * 4,
        None
    ]

    @validate(metadata_block_id=v.BlockGetRule)
    def utilize_get_metadata_block_get(self, metadata_block_id):
        return True

    @validate(metadata_block_id=v.BlockPutRule)
    def utilize_put_metadata_block_id(self, metadata_block_id):
        return True

    @validate(metadata_block_id=v.BlockPostRule)
    def utilize_post_metadata_block_id(self, metadata_block_id):
   
     return True

    @validate(metadata_block_id=v.BlockGetRuleNoneOk)
    def utilize_get_metadata_block_get_none_okay(self, metadata_block_id):
        return True

    @validate(metadata_block_id=v.BlockPutRuleNoneOk)
    def utilize_put_metadata_block_id_none_okay(self, metadata_block_id):
        return True

    @validate(metadata_block_id=v.BlockPostRuleNoneOk)
    def utilize_post_metadata_block_id_none_okay(self, metadata_block_id):
        return True

    @validate(req=v.Reque
stRule(v.BlockMarkerRule))
    def utilize_request(self, req, raiseme=False):
        if raiseme:
            raise RuntimeError('QUERY_STRING: {0}'.format(req.query_string))
        else:
            return True

    def test_block_id(self):

        for blockid in self.__class__.positive_cases:
            v.val_block_id(blockid)

        for blockid in self.__class__.negative_cases:
            with self.assertRaises(v.ValidationFailed):
                v.val_block_id()(blockid)

    def test_get_block_id(self):

        for blockid in self.__class__.positive_cases:
            self.utilize_get_metadata_block_get(blockid)

        for blockid in self.__class__.negative_cases:
            with self.assertRai