# -*- coding: utf-8; -*-

import configparser
import os
import shutil
import tempfile
from unittest import TestCase
from unittest.mock import patch

from rattail import config


class TestParseBoolFunc(TestCase):

    def test_none(self):
        self.assertIsNone(config.parse_bool(None))

    def test_true(self):
        self.assertIs(config.parse_bool(True), True)

    def test_false(self):
        self.assertIs(config.parse_bool(False), False)

    def test_string(self):
        self.assertTrue(config.parse_bool('true'))
        self.assertTrue(config.parse_bool('yes'))
        self.assertTrue(config.parse_bool('on'))
        self.assertTrue(config.parse_bool('1'))

        self.assertFalse(config.parse_bool('false'))
        self.assertFalse(config.parse_bool('no'))
        self.assertFalse(config.parse_bool('off'))
        self.assertFalse(config.parse_bool('0'))


class TestParseListFunc(TestCase):

    def test_none(self):
        value = config.parse_list(None)
        self.assertEqual(len(value), 0)

    def test_single_value(self):
        value = config.parse_list(u'foo')
        self.assertEqual(len(value), 1)
        self.assertEqual(value[0], u'foo')

    def test_single_value_padded_by_spaces(self):
        value = config.parse_list(u'   foo   ')
        self.assertEqual(len(value), 1)
        self.assertEqual(value[0], u'foo')

    def test_slash_is_not_a_separator(self):
        value = config.parse_list(u'/dev/null')
        self.assertEqual(len(value), 1)
        self.assertEqual(value[0], u'/dev/null')

    def test_multiple_values_separated_by_whitespace(self):
        value = config.parse_list(u'foo bar baz')
        self.assertEqual(len(value), 3)
        self.assertEqual(value[0], u'foo')
        self.assertEqual(value[1], u'bar')
        self.assertEqual(value[2], u'baz')

    def test_multiple_values_separated_by_commas(self):
        value = config.parse_list(u'foo,bar,baz')
        self.assertEqual(len(value), 3)
        self.assertEqual(value[0], u'foo')
        self.assertEqual(value[1], u'bar')
        self.assertEqual(value[2], u'baz')

    def test_multiple_values_separated_by_whitespace_and_commas(self):
        value = config.parse_list(u'  foo,   bar   baz')
        self.assertEqual(len(value), 3)
        self.assertEqual(value[0], u'foo')
        self.assertEqual(value[1], u'bar')
        self.assertEqual(value[2], u'baz')

    def test_multiple_values_separated_by_whitespace_and_commas_with_some_quoting(self):
        value = config.parse_list("""
        foo
        "C:\\some path\\with spaces\\and, a comma",
        baz
        """)
        self.assertEqual(len(value), 3)
        self.assertEqual(value[0], u'foo')
        self.assertEqual(value[1], u'C:\\some path\\with spaces\\and, a comma')
        self.assertEqual(value[2], u'baz')

    def test_multiple_values_separated_by_whitespace_and_commas_with_single_quotes(self):
        value = config.parse_list("""
        foo
        'C:\\some path\\with spaces\\and, a comma',
        baz
        """)
        self.assertEqual(len(value), 3)
        self.assertEqual(value[0], 'foo')
        self.assertEqual(value[1], 'C:\\some path\\with spaces\\and, a comma')
        self.assertEqual(value[2], 'baz')


class TestRattailConfig(TestCase):

    def setUp(self):
        self.tempdir = tempfile.mkdtemp()

    def tearDown(self):
        shutil.rmtree(self.tempdir)

    def write_file(self, fname, content):
        path = os.path.join(self.tempdir, fname)
        with open(path, 'wt') as f:
            f.write(content)
        return path

    def setup_files(self):
        self.site_path = self.write_file('site.conf', """
[rattail]
        """)

        self.host_path = self.write_file('host.conf', """
[rattail.config]
include = "{}"
        """.format(self.site_path))

        self.app_path = self.write_file('app.conf', """
[rattail.config]
include = "{}"
        """.format(self.host_path))

        self.custom_path = self.write_file('custom.conf', """
[rattail.config]
include = "%(here)s/app.conf"
        """)

    def test_init_defaults(self):
        cfg = config.RattailConfig()
        self.assertEqual(cfg.files_requested, [])
        self.assertEqual(cfg.files_read, [])

    def test_init_params(self):
        self.setup_files()

        # files
        cfg = config.RattailConfig()
        self.assertEqual(cfg.files_requested, [])
        self.assertEqual(cfg.files_read, [])
        cfg = config.RattailConfig(files=[self.site_path])
        self.assertEqual(cfg.files_requested, [self.site_path])
        self.assertEqual(cfg.files_read, [self.site_path])

        # usedb
        cfg = config.RattailConfig()
        self.assertFalse(cfg.usedb)
        cfg = config.RattailConfig(usedb=True)
        self.assertTrue(cfg.usedb)

        # preferdb
        cfg = config.RattailConfig()
        self.assertFalse(cfg.preferdb)
        cfg = config.RattailConfig(preferdb=True)
        self.assertTrue(cfg.preferdb)

    def test_read_file_with_recurse(self):
        self.setup_files()
        cfg = config.RattailConfig()
        cfg.read_file(self.custom_path, recurse=True)
        self.assertEqual(cfg.files_requested, [self.custom_path, self.app_path, self.host_path, self.site_path])
        self.assertEqual(cfg.files_read, [self.site_path, self.host_path, self.app_path, self.custom_path])

    def test_read_file_once_only(self):
        self.setup_files()

        another_path = self.write_file('another.conf', """
[rattail.config]
include = "{custom}" "{site}" "{app}" "{site}" "{custom}"
        """.format(custom=self.custom_path, app=self.app_path, site=self.site_path))

        cfg = config.RattailConfig()
        cfg.read_file(another_path, recurse=True)
        self.assertEqual(cfg.files_requested, [another_path, self.custom_path, self.app_path, self.host_path, self.site_path])
        self.assertEqual(cfg.files_read, [self.site_path, self.host_path, self.app_path, self.custom_path, another_path])

    def test_read_file_skip_missing(self):
        self.setup_files()
        bogus_path = '/tmp/does-not/exist'
        self.assertFalse(os.path.exists(bogus_path))

        another_path = self.write_file('another.conf', """
[rattail.config]
include = "{bogus}" "{app}" "{bogus}" "{site}"
        """.format(bogus=bogus_path, app=self.app_path, site=self.site_path))

        cfg = config.RattailConfig()
        cfg.read_file(another_path, recurse=True)
        self.assertEqual(cfg.files_requested, [another_path, bogus_path, self.app_path, self.host_path, self.site_path])
        self.assertEqual(cfg.files_read, [self.site_path, self.host_path, self.app_path, another_path])

    @patch('rattail.config.logging.config.fileConfig')
    def test_configure_logging(self, fileConfig):
        cfg = config.RattailConfig()

        # logging not configured by default
        cfg.configure_logging()
        self.assertFalse(fileConfig.called)

        # but config option can enable it
        cfg.set('rattail.config', 'configure_logging', 'true')
        cfg.configure_logging()
        self.assertEqual(fileConfig.call_count, 1)
        fileConfig.reset_mock()

        # invalid logging config is ignored
        fileConfig.side_effect = configparser.NoSectionError('loggers')
        cfg.configure_logging()
        self.assertEqual(fileConfig.call_count, 1)


class TestRattailConfigWrapper(TestCase):

    def setUp(self):
        self.tempdir = tempfile.mkdtemp()

    def tearDown(self):
        shutil.rmtree(self.tempdir)

    def write_file(self, filename, content):
        path = os.path.join(self.tempdir, filename)
        with open(path, 'wt') as f:
            f.write(content)
        return path

    def test_legacy(self):
        myconfig = config.RattailConfigWrapper()
        self.assertEqual(repr(myconfig), "RattailConfigWrapper(style=legacy)")

    def test_configuration(self):
        myfile = self.write_file('my.conf', """\
[rattail.config]
use_configuration = true
""")

        myconfig = config.RattailConfigWrapper(files=[myfile])
        self.assertEqual(repr(myconfig), "RattailConfigWrapper(style=configuration)")

    def test_wuttaconfig(self):
        myfile = self.write_file('my.conf', """\
[rattail.config]
use_wuttaconfig = true
""")

        myconfig = config.RattailConfigWrapper(files=[myfile])
        self.assertEqual(repr(myconfig), "RattailConfigWrapper(style=wuttaconfig)")

    def test_getattr(self):
        myconfig = config.RattailConfigWrapper()
        self.assertIs(myconfig.files_read, myconfig.config.files_read)

    def test_setattr(self):
        myconfig = config.RattailConfigWrapper()
        self.assertFalse(hasattr(myconfig.config, 'foo'))
        myconfig.foo = 'bar'
        self.assertEqual(myconfig.config.foo, 'bar')


class TestRattailWuttaConfig(TestCase):

    def setUp(self):
        self.tempdir = tempfile.mkdtemp()

    def tearDown(self):
        shutil.rmtree(self.tempdir)

    def write_file(self, filename, content):
        path = os.path.join(self.tempdir, filename)
        with open(path, 'wt') as f:
            f.write(content)
        return path

    def test_prioritized_files(self):
        first = self.write_file('first.conf', """\
[foo]
bar = 1
""")

        second = self.write_file('second.conf', """\
[rattail.config]
require = %(here)s/first.conf
""")

        myconfig = config.RattailWuttaConfig(files=[second])
        files = myconfig.prioritized_files
        self.assertEqual(len(files), 2)
        self.assertEqual(files[0], second)
        self.assertEqual(files[1], first)
        self.assertIs(files, myconfig.get_prioritized_files())

    def test_setdefault(self):
        myconfig = config.RattailWuttaConfig()

        # nb. the tests below are effectively testing the custom get()
        # method in addition to setdefault()

        # value is empty by default
        self.assertIsNone(myconfig.get('foo.bar'))
        self.assertIsNone(myconfig.get('foo', 'bar'))

        # but we can change that by setting default
        myconfig.setdefault('foo.bar', 'baz')
        self.assertEqual(myconfig.get('foo.bar'), 'baz')
        self.assertEqual(myconfig.get('foo', 'bar'), 'baz')

        # also can set a default via section, option (as well as key)
        self.assertIsNone(myconfig.get('foo.blarg'))
        myconfig.setdefault('foo' ,'blarg', 'blast')
        self.assertEqual(myconfig.get('foo.blarg'), 'blast')
        self.assertEqual(myconfig.get('foo', 'blarg'), 'blast')

        # error is raised if args are ambiguous
        self.assertRaises(ValueError, myconfig.setdefault, 'foo', 'bar', 'blarg', 'blast')

        # try that for get() too
        self.assertRaises(ValueError, myconfig.get, 'foo', 'bar', 'blarg', 'blast')

    def test_getbool(self):
        myconfig = config.RattailWuttaConfig()
        self.assertFalse(myconfig.getbool('foo.bar'))
        myconfig.setdefault('foo.bar', 'true')
        self.assertTrue(myconfig.getbool('foo.bar'))

    def test_getint(self):
        myconfig = config.RattailWuttaConfig()
        self.assertIsNone(myconfig.getint('foo.bar'))
        myconfig.setdefault('foo.bar', '42')
        self.assertEqual(myconfig.getint('foo.bar'), 42)

    def test_getlist(self):
        myconfig = config.RattailWuttaConfig()
        self.assertEqual(myconfig.getlist('foo.bar'), [])
        myconfig.setdefault('foo.bar', 'hello world')
        self.assertEqual(myconfig.getlist('foo.bar'), ['hello', 'world'])

    def test_parse_list(self):
        myconfig = config.RattailWuttaConfig()
        self.assertEqual(myconfig.parse_list(None), [])
        self.assertEqual(myconfig.parse_list('hello world'), ['hello', 'world'])
