from ipaddress import ip_address
import os
from pathlib import PosixPath
import string

from django.conf import settings
from django.core.exceptions import ValidationError
from django.test import SimpleTestCase

from console_base.validators import *


# -------------------------------------------------------------------------
class TestValidateNoSpaces(SimpleTestCase):
    def test_spaces_fails(self):
        with self.assertRaises(ValidationError):
            validate_no_spaces('Tom Cat')

    def test_no_spaces_succeeds(self):
        self.assertEqual(validate_no_spaces('TomCat'), None)


# -------------------------------------------------------------------------
class TestValidateMAC(SimpleTestCase):
    def test_gratuitous_fails(self):
        with self.assertRaises(ValidationError):
            validate_mac_address('00:00:00:00:00:00')

    def test_incomplete_fails(self):
        with self.assertRaises(ValidationError):
            validate_mac_address('00:00:00:00:50:93')


# -------------------------------------------------------------------------
class TestValidatePorts(SimpleTestCase):
    def test_invalid_ports(self):
        ports = (
            '-10',
            '0',
            '32s',
            '500:350',
            '500:500',
            '1433,500:500',
            '75536',
        )
        for port in ports:
            with self.assertRaises(
                ValidationError, msg=f'Invalid port {port} should raise exception'
            ):
                validate_ports(port)

    def test_valid_ports(self):
        ports = (
            '32',
            '500:750',
            '65535',
            '443,143,587,25',
        )
        for port in ports:
            self.assertIsNone(validate_ports(port))


# -------------------------------------------------------------------------
class TestValidateNoPunctuation(SimpleTestCase):
    def test_punctuation_fails(self):
        punctuation = string.punctuation.replace('_', '')
        punctuation = punctuation.replace('|', '')
        punctuation = punctuation.replace('-', '')
        for char in punctuation:
            with self.assertRaises(ValidationError, msg=f'"{char}" is an illegal character'):
                validate_legal_punctuation('Tom%sCat' % char)

    def test_allow_underscore(self):
        self.assertEqual(validate_legal_punctuation('Tom_Cat'), None)

    def test_allow_period(self):
        self.assertEqual(validate_legal_punctuation('Tom_Cat', skip=('.',)), None)


# -------------------------------------------------------------------------
class TestValidateMimesMethodsStatus(SimpleTestCase):
    def test_invalid_mimetypes(self):
        for mime in (
            'candy/canes',
            'boxcar',
            'ax/css',
        ):
            with self.assertRaises(ValidationError):
                validate_mimetype(mime)

    def test_invalid_methods(self):
        for method in (
            'YANK',
            'SCRATCHHOE',
        ):
            with self.assertRaises(ValidationError):
                validate_http_method(method)

    def test_invalid_status(self):
        for status in (
            'SUCCESS',
            '599',
            '1100',
        ):
            with self.assertRaises(ValidationError):
                validate_http_method(status)


# -------------------------------------------------------------------------
class TestValidateMultipleWords(SimpleTestCase):
    def test_multiple_words_fails(self):
        with self.assertRaises(ValidationError):
            validate_multiple_words('TheCowsintheCorn!')

    def test_multiple_words_succeeds(self):
        self.assertEqual(validate_multiple_words('The Cows in the Corn!'), None)


# -------------------------------------------------------------------------
class TestValidateIPDomain(SimpleTestCase):
    def test_validate_bad_domain(self):
        with self.assertRaises(ValidationError):
            validate_ip_domain('dellcom')

    def test_validate_bad_ip(self):
        with self.assertRaises(ValidationError):
            validate_ip_domain('256.389.1.27')

    def test_validate_bad_ip_block(self):
        with self.assertRaises(ValidationError):
            validate_ip_domain('10.0.0.0/55')

    def test_validate_good_hostname(self):
        self.assertEqual(validate_ip_domain('shipping.studio.lan'), None)

    def test_validate_good_ip(self):
        self.assertEqual(validate_ip_domain('10.3.5.150'), None)

    def test_validate_good_ip_block(self):
        self.assertEqual(validate_ip_domain('10.0.0.0/8'), None)


# -------------------------------------------------------------------------
class TestValidateNonRoutableIPs(SimpleTestCase):
    def test_reject_non_routable_ips(self):
        for ip in (
            '127.0.0.1',
            '127.0.5.10',
            '127.0.5.10',
            '0.0.0.0',
            '240.0.1.20',
        ):
            try:
                validate_routable_ip(ip_address(ip))
            except ValidationError:
                continue
            raise ValueError(f'{ip} is not routable')


# -------------------------------------------------------------------------
class TestValidateHyphenatedIP(SimpleTestCase):
    def test_reject_invalid_address(self):
        for ipr in (
            '10.3.5.100-10.3.5.10',
            '10.3.5.1',
            '10.4.5.1 10.4.5.10',
            '127.0.0.1-127.0.0.10',
        ):
            with self.assertRaises(ValidationError):
                validate_hyphenated_ip_range(ipr)


# -------------------------------------------------------------------------
class TestValidateDomain(SimpleTestCase):
    def test_validate_bad_domain(self):
        with self.assertRaises(ValidationError):
            validate_domain_url('dellcom')

        with self.assertRaises(ValidationError):
            validate_domain_url('dellcom/support')

    def test_validate_http(self):
        rule = 'http://www.dell.com'
        with self.assertRaises(ValidationError):
            validate_domain_url(rule)

        rule = 'https://dell.com'
        with self.assertRaises(ValidationError):
            validate_domain_url(rule)

    def test_validate_www(self):
        rule = 'www.dell.com'
        with self.assertRaises(ValidationError):
            validate_domain_url(rule)

    def test_validate_valid_chars(self):
        rule = 'd@ll.com'
        with self.assertRaises(ValidationError):
            validate_domain_url(rule)

    def test_validate_valid_spaces(self):
        rule = 'd ll.com'
        with self.assertRaises(ValidationError):
            validate_domain_url(rule)

    def test_localhost(self):
        self.assertEqual(validate_domain_url('localhost'), None)

    def test_default(self):
        self.assertEqual(validate_domain_url('default'), None)


# -------------------------------------------------------------------------
class TestValidateRegex(SimpleTestCase):
    def test_validate_short_regex(self):
        rule = 'del'
        with self.assertRaisesMessage(ValidationError, '"{}" is too short'.format(rule)):
            validate_regex(rule)

    def test_validate_invalid_regex_space(self):
        rule = 'del cats'
        with self.assertRaisesMessage(ValidationError, 'Please do not use spaces here.'):
            validate_regex(rule)

    def test_validate_valid_regex(self):
        rule = 'happy-?harry'
        self.assertEqual(validate_regex(rule), None)

        rule = 'sports[-_\+]?car'
        self.assertEqual(validate_regex(rule), None)


# -------------------------------------------------------------------------
class TestValidatePhrase(SimpleTestCase):
    def test_validate_short_phrase(self):
        rule = 'dell'
        with self.assertRaisesMessage(ValidationError, '"{}" is too short'.format(rule)):
            validate_phrase(rule)

    def test_validate_short_phrase2(self):
        rule = 'bellry'
        with self.assertRaisesMessage(
            ValidationError, f'"{rule}" is too short. Consider prepending or appending a space'
        ):
            validate_phrase(rule)

    def test_validate_special_chars(self):
        rule = 'bellfry-cats'
        with self.assertRaisesMessage(
            ValidationError, f'"{rule}" - replace punctuation characters with spaces'
        ):
            validate_phrase(rule)

    def test_validate_valid_phrase(self):
        rule = 'candy canes'
        self.assertEqual(validate_phrase(rule), None)


# -------------------------------------------------------------------------
class TestMultipleWords(SimpleTestCase):
    def test_multiple_words1(self):
        with self.assertRaisesMessage(ValidationError, 'Please provide more detailed information'):
            validate_multiple_words('alloneword')

    def test_multiple_words2(self):
        self.assertEqual(validate_multiple_words('this is several words'), None)


# -------------------------------------------------------------------------
class TestValidateFilePath(SimpleTestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.file = f'{settings.TMP_DIR}/file.txt'

    def tearDown(self):
        try:
            os.remove(self.file)
        except FileNotFoundError:
            pass

    def test_invalid_file_path(self):
        self.assertFalse(PosixPath(self.file).is_file())
        with self.assertRaises(ValidationError):
            validate_file_path(self.file)

    def test_valid_file_path(self):
        with open(self.file, 'w'):
            pass

        self.assertIsNone(validate_file_path(self.file))


# -------------------------------------------------------------------------
class TestValidateDirectory(SimpleTestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.directory = f'{settings.TMP_DIR}/categories'

    def tearDown(self):
        try:
            os.rmdir(self.directory)
        except FileNotFoundError:
            pass

    def test_invalid_directory(self):
        self.assertFalse(PosixPath(self.directory).is_dir())
        with self.assertRaises(ValidationError):
            validate_directory(self.directory)

    def test_valid_directory(self):
        os.mkdir(self.directory)
        self.assertIsNone(validate_directory(self.directory))


# -------------------------------------------------------------------------
class TestValidateLegalTenantChange(SimpleTestCase):
    def test_no_tenant_changes(self):
        self.assertDictEqual(confirm_legal_tenant_change({}), {})

    def test_universal_record_no_tenant_changed(self):
        fields = {
            'company': {
                'old': None,
                'new': None,
            },
            'policy': {
                'old': None,
                'new': None,
            },
        }
        self.assertDictEqual(confirm_legal_tenant_change(fields), {})

    def test_company_tenant_changed(self):
        fields = {'company': {'old': None, 'new': 5}}
        error_data = confirm_legal_tenant_change(fields)
        self.assertEqual(error_data['tenant'], 'company')
        self.assertIn('Company', error_data['message'])

    def test_policy_tenant_changed(self):
        fields = {'policy': {'old': None, 'new': 5}}
        error_data = confirm_legal_tenant_change(fields)
        self.assertEqual(error_data['tenant'], 'policy')
        self.assertIn('Policy', error_data['message'])

    def test_policy_and_company_changed(self):
        """
        If Policy and Company changed, but record was not universal before,
        then no errors should be returned.
        """
        fields = {
            'policy': {
                'old': '9c1f7978-73fa-11ec-8b8c-7085c27cfb9b',
                'new': 'a4aef2db-73fa-11ec-919a-7085c27cfb9b',
            },
            'company': {
                'old': None,
                'new': '9340bd30-73fa-11ec-873c-7085c27cfb9b',
            },
        }
        self.assertDictEqual(confirm_legal_tenant_change(fields), {})

    def test_policy_and_company_added(self):
        """
        If Policy and Company are added, then _one_ of the
        tenant fields should be returned; doesn't matter which
        """
        fields = {
            'policy': {
                'old': None,
                'new': 'a4aef2db-73fa-11ec-919a-7085c27cfb9b',
            },
            'company': {
                'old': None,
                'new': '9340bd30-73fa-11ec-873c-7085c27cfb9b',
            },
        }
        error_data = confirm_legal_tenant_change(fields)
        self.assertIn(error_data['code'], 'illegal_tenant')


# -------------------------------------------------------------------------
class TestRemoteURLValidator(SimpleTestCase):
    def test_local_ips(self):
        url = RemoteURLValidator()
        for ip in (
            '10.3.5.10',
            '10.90.25.7',
        ):
            with self.assertRaises(ValidationError):
                url(ip)

    def test_local_dns_names(self):
        url = RemoteURLValidator()
        for ip in (
            'draw.bridge',
            'localhost',
            'log.cabin',
        ):
            with self.assertRaises(ValidationError):
                url(ip)
