import os
import unittest
from decimal import Decimal, InvalidOperation
import json

try:
    # python 3.4+ should use builtin unittest.mock not mock package
    from unittest.mock import patch
    from unittest import mock
except ImportError:
    from mock import patch
    import mock

import sys

from envs import env

class EnvTestCase(unittest.TestCase):
    def setUp(self):
        # Integer
        os.environ.setdefault('VALID_INTEGER', '1')
        os.environ.setdefault('INVALID_INTEGER', '["seven"]')
        # String
        os.environ.setdefault('VALID_STRING', 'seven')
        # Boolean
        os.environ.setdefault('VALID_BOOLEAN', 'True')
        os.environ.setdefault('VALID_BOOLEAN_FALSE', 'false')
        os.environ.setdefault('INVALID_BOOLEAN', 'seven')
        # List
        os.environ.setdefault('VALID_LIST', "['1','2','3']")
        os.environ.setdefault('INVALID_LIST', "1")
        # Tuple
        os.environ.setdefault('VALID_TUPLE', "('True','FALSE')")
        os.environ.setdefault('INVALID_TUPLE', '1')
        # Dict
        os.environ.setdefault('VALID_DICT', "{'first_name':'Suge'}")
        os.environ.setdefault('INVALID_DICT', 'Aaron Rogers')
        # Float
        os.environ.setdefault('VALID_FLOAT', "5.0")
        os.environ.setdefault('INVALID_FLOAT', '[5.0]')
        # Decimal
        os.environ.setdefault('VALID_DECIMAL', "2.39")
        os.environ.setdefault('INVALID_DECIMAL', "FOOBAR")

    def test_integer_valid(self):
        self.assertEqual(1, env('VALID_INTEGER', var_type='integer'))

    def test_integer_invalid(self):
        with self.assertRaises(TypeError) as vm:
            env('INVALID_INTEGER', var_type='integer')

    def test_wrong_var_type(self):
        with self.assertRaises(ValueError) as vm:
            env('INVALID_INTEGER', var_type='set')

    def test_string_valid(self):
        self.assertEqual('seven', env('VALID_STRING'))

    def test_boolean_valid(self):
        self.assertEqual(True, env('VALID_BOOLEAN', var_type='boolean'))

    def test_boolean_valid_false(self):
        self.assertEqual(False, env('VALID_BOOLEAN_FALSE', var_type='boolean'))

    def test_boolean_invalid(self):
        with self.assertRaises(ValueError) as vm:
            env('INVALID_BOOLEAN', var_type='boolean')

    def test_list_valid(self):
        self.assertEqual(['1', '2', '3'], env('VALID_LIST', var_type='list'))

    def test_list_invalid(self):
        with self.assertRaises(TypeError) as vm:
            env('INVALID_LIST', var_type='list')

    def test_tuple_valid(self):
        self.assertEqual(('True', 'FALSE'), env('VALID_TUPLE', var_type='tuple'))

    def test_tuple_invalid(self):
        with self.assertRaises(TypeError) as vm:
            env('INVALID_TUPLE', var_type='tuple')

    def test_dict_valid(self):
        self.assertEqual({'first_name': 'Suge'}, env('VALID_DICT', var_type='dict'))

    def test_dict_invalid(self):
        with self.assertRaises(SyntaxError) as vm:
            env('INVALID_DICT', var_type='dict')

    def test_float_valid(self):
        self.assertEqual(5.0, env('VALID_FLOAT', var_type='float'))

    def test_float_invalid(self):
        with self.assertRaises(TypeError) as vm:
            env('INVALID_FLOAT', var_type='float')

    def test_decimal_valid(self):
        self.assertEqual(Decimal('2.39'), env('VALID_DECIMAL', var_type='decimal'))

    def test_decimal_invalid(self):
        with self.assertRaises(ArithmeticError) as vm:
            env('INVALID_DECIMAL', var_type='decimal')

    def test_defaults(self):
        self.assertEqual(env('HELLO', 5, var_type='integer'), 5)
        self.assertEqual(env('HELLO', 5.0, var_type='float'), 5.0)
        self.assertEqual(env('HELLO', [], var_type='list'), [])
        self.assertEqual(env('HELLO', {}, var_type='dict'), {})
        self.assertEqual(env('HELLO', (), var_type='tuple'), ())
        self.assertEqual(env('HELLO', 'world'), 'world')
        self.assertEqual(env('HELLO', False, var_type='boolean'), False)
        self.assertEqual(env('HELLO', 'False', var_type='boolean'), False)
        self.assertEqual(env('HELLO', 'true', var_type='boolean'), True)
        self.assertEqual(env('HELLO', Decimal('3.14'), var_type='decimal'), Decimal('3.14'))

'''
Each CLI Test must be run outside of test suites in isolation
since Click CLI Runner alters the global context
'''
def setup_CliRunner(test_func):
    '''
    Decorator to initialize environment for CliRunner.
    '''
    def wrapper():
        from click.testing import CliRunner
        try:
            from cli import envs as cli_envs
        except ImportError:
            from .cli import envs as cli_envs

        try:
            from cli import envs
        except ImportError:
            from .cli import envs

        test_func()

    return wrapper

@mock.patch.object(sys, 'argv', ["list-envs"])
@setup_CliRunner
def test_list_envs():
    os.environ.setdefault('DEBUG', 'True')

    runner = CliRunner()
    result = runner.invoke(envs, ['list-envs', '--settings-file', 'envs.test_settings', '--keep-result', 'True'], catch_exceptions=False)

    output_expected = [{"default": None, "value": None, "var_type": "string", "key": "DATABASE_URL"},{"default": False, "value": "True", "var_type": "boolean", "key": "DEBUG"},{"default": [], "value": None, "var_type": "list", "key": "MIDDLEWARE"},{}]

    with open('.envs_result', 'r') as f:
        output_actual = json.load(f)

    exit_code_expected = 0
    exit_code_actual = result.exit_code


    assert exit_code_actual == exit_code_expected
    assert output_actual == output_expected





if __name__ == '__main__':
    unittest.main()

