import re
import pytz
import pytest

from datetime import date, datetime

from flask_restx import inputs


class Iso8601DateTest(object):
    @pytest.mark.parametrize(
        "value,expected",
        [
            ("2011-01-01", date(2011, 1, 1)),
            ("2011-01-01T00:00:00+00:00", date(2011, 1, 1)),
            ("2011-01-01T23:59:59+00:00", date(2011, 1, 1)),
            ("2011-01-01T23:59:59.001000+00:00", date(2011, 1, 1)),
            ("2011-01-01T23:59:59+02:00", date(2011, 1, 1)),
        ],
    )
    def test_valid_values(self, value, expected):
        assert inputs.date_from_iso8601(value) == expected

    def test_error(self):
        with pytest.raises(ValueError):
            inputs.date_from_iso8601("2008-13-13")

    def test_schema(self):
        assert inputs.date_from_iso8601.__schema__ == {
            "type": "string",
            "format": "date",
        }


class Iso8601DatetimeTest(object):
    @pytest.mark.parametrize(
        "value,expected",
        [
            ("2011-01-01", datetime(2011, 1, 1)),
            ("2011-01-01T00:00:00+00:00", datetime(2011, 1, 1, tzinfo=pytz.utc)),
            (
                "2011-01-01T23:59:59+00:00",
                datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.utc),
            ),
            (
                "2011-01-01T23:59:59.001000+00:00",
                datetime(2011, 1, 1, 23, 59, 59, 1000, tzinfo=pytz.utc),
            ),
            (
                "2011-01-01T23:59:59+02:00",
                datetime(2011, 1, 1, 21, 59, 59, tzinfo=pytz.utc),
            ),
        ],
    )
    def test_valid_values(self, value, expected):
        assert inputs.datetime_from_iso8601(value) == expected

    def test_error(self):
        with pytest.raises(ValueError):
            inputs.datetime_from_iso8601("2008-13-13")

    def test_schema(self):
        assert inputs.datetime_from_iso8601.__schema__ == {
            "type": "string",
            "format": "date-time",
        }


class Rfc822DatetimeTest(object):
    @pytest.mark.parametrize(
        "value,expected",
        [
            ("Sat, 01 Jan 2011", datetime(2011, 1, 1, tzinfo=pytz.utc)),
            ("Sat, 01 Jan 2011 00:00", datetime(2011, 1, 1, tzinfo=pytz.utc)),
            ("Sat, 01 Jan 2011 00:00:00", datetime(2011, 1, 1, tzinfo=pytz.utc)),
            ("Sat, 01 Jan 2011 00:00:00 +0000", datetime(2011, 1, 1, tzinfo=pytz.utc)),
            ("Sat, 01 Jan 2011 00:00:00 -0000", datetime(2011, 1, 1, tzinfo=pytz.utc)),
            (
                "Sat, 01 Jan 2011 23:59:59 -0000",
                datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.utc),
            ),
            (
                "Sat, 01 Jan 2011 21:00:00 +0200",
                datetime(2011, 1, 1, 19, 0, 0, tzinfo=pytz.utc),
            ),
            (
                "Sat, 01 Jan 2011 21:00:00 -0200",
                datetime(2011, 1, 1, 23, 0, 0, tzinfo=pytz.utc),
            ),
        ],
    )
    def test_valid_values(self, value, expected):
        assert inputs.datetime_from_rfc822(value) == expected

    def test_error(self):
        with pytest.raises(ValueError):
            inputs.datetime_from_rfc822("Fake, 01 XXX 2011")


class NetlocRegexpTest(object):
    @pytest.mark.parametrize(
        "netloc,kwargs",
        [
            ("localhost", {"localhost": "localhost"}),
            ("example.com", {"domain": "example.com"}),
            ("www.example.com", {"domain": "www.example.com"}),
            ("www.example.com:8000", {"domain": "www.example.com", "port": "8000"}),
            ("valid-with-hyphens.com", {"domain": "valid-with-hyphens.com"}),
            ("subdomain.example.com", {"domain": "subdomain.example.com"}),
            ("200.8.9.10", {"ipv4": "200.8.9.10"}),
            ("200.8.9.10:8000", {"ipv4": "200.8.9.10", "port": "8000"}),
            ("valid-----hyphens.com", {"domain": "valid-----hyphens.com"}),
            ("foo:bar@example.com", {"auth": "foo:bar", "domain": "example.com"}),
            ("foo:@example.com", {"auth": "foo:", "domain": "example.com"}),
            ("foo@example.com", {"auth": "foo", "domain": "example.com"}),
            (
                "foo:@2001:db8:85a3::8a2e:370:7334",
                {"auth": "foo:", "ipv6": "2001:db8:85a3::8a2e:370:7334"},
            ),
            (
                "[1fff:0:a88:85a3::ac1f]:8001",
                {"ipv6": "1fff:0:a88:85a3::ac1f", "port": "8001"},
            ),
            ("foo2:qd1%r@example.com", {"auth": "foo2:qd1%r", "domain": "example.com"}),
        ],
    )
    def test_match(self, netloc, kwargs):
        match = inputs.netloc_regex.match(netloc)
        assert match, "Should match {0}".format(netloc)
        expected = {
            "auth": None,
            "domain": None,
            "ipv4": None,
            "ipv6": None,
            "localhost": None,
            "port": None,
        }
        expected.update(kwargs)
        assert match.groupdict() == expected


class URLTest(object):
    def assert_bad_url(self, validator, value, details=None):
        msg = "{0} is not a valid URL"
        with pytest.raises(ValueError) as cm:
            validator(value)
        if details:
            assert str(cm.value) == ". ".join((msg, details)).format(value)
        else:
            assert str(cm.value).startswith(msg.format(value))

    @pytest.mark.parametrize(
        "url",
        [
            "http://www.djangoproject.com/",
            "http://example.com/",
            "http://www.example.com/",
            "http://www.example.com/test",
            "http://valid-with-hyphens.com/",
            "http://subdomain.example.com/",
            "http://valid-----hyphens.com/",
            "http://example.com?something=value",
            "http://example.com/index.php?something=value&another=value2",
        ],
    )
    def test_valid_values_default(self, url):
        validator = inputs.URL()
        assert validator(url) == url

    @pytest.mark.parametrize(
        "url",
        [
            "foo",
            "http://",
            "http://example",
            "http://example.",
            "http://.com",
            "http://invalid-.com",
            "http://-invalid.com",
            "http://inv-.alid-.com",
            "http://inv-.-alid.com",
            "foo bar baz",
            "foo \u2713",
            "http://@foo:bar@example.com",
            "http://:bar@example.com",
            "http://bar:bar:bar@example.com",
            "http://300:300:300:300",
            "http://example.com:70000",
            "http://example.com:0000",
        ],
    )
    def test_bad_urls(self, url):
        # Test with everything enabled to ensure bad URL are really detected
        validator = inputs.URL(ip=True, auth=True, port=True)
        self.assert_bad_url(validator, url)
        # msg = '{0} is not a valid URL'.format(url)
        # with pytest.raises(ValueError) as cm:
        #     validator(url)
        # assert str(cm.exception).startswith(msg)

    @pytest.mark.parametrize(
        "url",
        [
            "google.com",
            "domain.google.com",
            "kevin:pass@google.com/path?query",
            "google.com/path?\u2713",
        ],
    )
    def test_bad_urls_with_suggestion(self, url):
        validator = inputs.URL()
        self.assert_bad_url(validator, url, "Did you mean: http://{0}")

    @pytest.mark.parametrize(
        "url",
        [
            "http://200.8.9.10/",
            "http://foo:bar@200.8.9.10/",
            "http://200.8.9.10:8000/test",
            "http://2001:db8:85a3::8a2e:370:7334",
            "http://[1fff:0:a88:85a3::ac1f]:8001",
        ],
    )
    def test_reject_ip(self, url):
        validator = inputs.URL()
        self.assert_bad_url(validator, url, "IP is not allowed")

    @pytest.mark.parametrize(
        "url",
        [
            "http://200.8.9.10/",
            "http://200.8.9.10/test",
            "http://2001:db8:85a3::8a2e:370:7334",
            "http://[1fff:0:a88:85a3::ac1f]",
        ],
    )
    def test_allow_ip(self, url):
        validator = inputs.URL(ip=True)
        assert validator(url) == url

    @pytest.mark.parametrize(
        "url",
        [
            "http://foo:bar@200.8.9.10/",
            "http://foo:@2001:db8:85a3::8a2e:370:7334",
            "http://foo:bar@[1fff:0:a88:85a3::ac1f]:8001",
            "http://foo:@2001:db8:85a3::8a2e:370:7334",
            "http://foo2:qd1%r@example.com",
        ],
    )
    def test_reject_auth(self, url):
        # Test with IP and port to ensure only auth is rejected
        validator = inputs.URL(ip=True, port=True)
        self.assert_bad_url(validator, url, "Authentication is not allowed")

    @pytest.mark.parametrize(
        "url",
        [
            "http://foo:bar@example.com",
            "http://foo:@example.com",
            "http://foo@example.com",
        ],
    )
    def test_allow_auth(self, url):
        validator = inputs.URL(auth=True)
        assert validator(url) == url

    @pytest.mark.parametrize(
        "url",
        [
            "http://localhost",
            "http://127.0.0.1",
            "http://127.0.1.1",
            "http://::1",
        ],
    )
    def test_reject_local(self, url):
        # Test with IP and port to ensure only auth is rejected
        validator = inputs.URL(ip=True)
        self.assert_bad_url(validator, url, "Localhost is not allowed")

    @pytest.mark.parametrize(
        "url",
        [
            "http://localhost",
            "http://127.0.0.1",
            "http://127.0.1.1",
            "http://::1",
        ],
    )
    def test_allow_local(self, url):
        validator = inputs.URL(ip=True, local=True)
        assert validator(url) == url

    @pytest.mark.parametrize(
        "url",
        [
            "http://200.8.9.10:8080/",
            "http://foo:bar@200.8.9.10:8080/",
            "http://foo:bar@[1fff:0:a88:85a3::ac1f]:8001",
        ],
    )
    def test_reject_port(self, url):
        # Test with auth and port to ensure only port is rejected
        validator = inputs.URL(ip=True, auth=True)
        self.assert_bad_url(validator, url, "Custom port is not allowed")

    @pytest.mark.parametrize(
        "url",
        [
            "http://example.com:80",
            "http://example.com:8080",
            "http://www.example.com:8000/test",
        ],
    )
    def test_allow_port(self, url):
        validator = inputs.URL(port=True)
        assert validator(url) == url

    @pytest.mark.parametrize(
        "url",
        [
            "sip://somewhere.com",
            "irc://somewhere.com",
        ],
    )
    def test_valid_restricted_schemes(self, url):
        validator = inputs.URL(schemes=("sip", "irc"))
        assert validator(url) == url

    @pytest.mark.parametrize(
        "url",
        [
            "http://somewhere.com",
            "https://somewhere.com",
        ],
    )
    def test_invalid_restricted_schemes(self, url):
        validator = inputs.URL(schemes=("sip", "irc"))
        self.assert_bad_url(validator, url, "Protocol is not allowed")

    @pytest.mark.parametrize(
        "url",
        [
            "http://example.com",
            "http://example.com/test/",
            "http://www.example.com/",
            "http://www.example.com/test",
        ],
    )
    def test_valid_restricted_domains(self, url):
        validator = inputs.URL(domains=["example.com", "www.example.com"])
        assert validator(url) == url

    @pytest.mark.parametrize(
        "url",
        [
            "http://somewhere.com",
            "https://somewhere.com",
        ],
    )
    def test_invalid_restricted_domains(self, url):
        validator = inputs.URL(domains=["example.com", "www.example.com"])
        self.assert_bad_url(validator, url, "Domain is not allowed")

    @pytest.mark.parametrize(
        "url",
        [
            "http://somewhere.com",
            "https://somewhere.com",
        ],
    )
    def test_valid_excluded_domains(self, url):
        validator = inputs.URL(exclude=["example.com", "www.example.com"])
        assert validator(url) == url

    @pytest.mark.parametrize(
        "url",
        [
            "http://example.com",
            "http://example.com/test/",
            "http://www.example.com/",
            "http://www.example.com/test",
        ],
    )
    def test_excluded_domains(self, url):
        validator = inputs.URL(exclude=["example.com", "www.example.com"])
        self.assert_bad_url(validator, url, "Domain is not allowed")

    def test_check(self):
        validator = inputs.URL(check=True, ip=True)
        assert (
            validator("http://www.google.com") == "http://www.google.com"
        ), "Should check domain"

        # This test will fail on a network where this address is defined
        self.assert_bad_url(
            validator,
            "http://this-domain-should-not-exist.com",
            "Domain does not exists",
        )

    def test_schema(self):
        assert inputs.URL().__schema__ == {"type": "string", "format": "url"}


class UrlTest(object):
    @pytest.mark.parametrize(
        "url",
        [
            "http://www.djangoproject.com/",
            "http://localhost/",
            "http://example.com/",
            "http://www.example.com/",
            "http://www.example.com:8000/test",
            "http://valid-with-hyphens.com/",
            "http://subdomain.example.com/",
            "http://200.8.9.10/",
            "http://200.8.9.10:8000/test",
            "http://valid-----hyphens.com/",
            "http://example.com?something=value",
            "http://example.com/index.php?something=value&another=value2",
            "http://foo:bar@example.com",
            "http://foo:@example.com",
            "http://foo@example.com",
            "http://foo:@2001:db8:85a3::8a2e:370:7334",
            "http://foo2:qd1%r@example.com",
        ],
    )
    def test_valid_url(self, url):
        assert inputs.url(url) == url

    @pytest.mark.parametrize(
        "url",
        [
            "foo",
            "http://",
            "http://example",
            "http://example.",
            "http://.com",
            "http://invalid-.com",
            "http://-invalid.com",
            "http://inv-.alid-.com",
            "http://inv-.-alid.com",
            "foo bar baz",
            "foo \u2713",
            "http://@foo:bar@example.com",
            "http://:bar@example.com",
            "http://bar:bar:bar@example.com",
            "http://300:300:300:300",
            "http://example.com:70000",
        ],
    )
    def test_bad_url(self, url):
        with pytest.raises(ValueError) as cm:
            inputs.url(url)
        assert str(cm.value).startswith("{0} is not a valid URL".format(url))

    @pytest.mark.parametrize(
        "url",
        [
            "google.com",
            "domain.google.com",
            "kevin:pass@google.com/path?query",
            "google.com/path?\u2713",
        ],
    )
    def test_bad_url_with_suggestion(self, url):
        with pytest.raises(ValueError) as cm:
            inputs.url(url)
        assert str(
            cm.value
        ) == "{0} is not a valid URL. Did you mean: http://{0}".format(url)

    def test_schema(self):
        assert inputs.url.__schema__ == {"type": "string", "format": "url"}


class IPTest(object):
    @pytest.mark.parametrize(
        "value",
        [
            "200.8.9.10",
            "127.0.0.1",
            "2001:db8:85a3::8a2e:370:7334",
            "::1",
        ],
    )
    def test_valid_value(self, value):
        assert inputs.ip(value) == value

    @pytest.mark.parametrize(
        "value",
        [
            "foo",
            "http://",
            "http://example",
            "http://example.",
            "http://.com",
            "http://invalid-.com",
            "http://-invalid.com",
            "http://inv-.alid-.com",
            "http://inv-.-alid.com",
            "foo bar baz",
            "foo \u2713",
            "http://@foo:bar@example.com",
            "http://:bar@example.com",
            "http://bar:bar:bar@example.com",
            "127.0",
        ],
    )
    def test_bad_value(self, value):
        with pytest.raises(ValueError):
            inputs.ip(value)

    def test_schema(self):
        assert inputs.ip.__schema__ == {"type": "string", "format": "ip"}


class IPv4Test(object):
    @pytest.mark.parametrize(
        "value",
        [
            "200.8.9.10",
            "127.0.0.1",
        ],
    )
    def test_valid_value(self, value):
        assert inputs.ipv4(value) == value

    @pytest.mark.parametrize(
        "value",
        [
            "2001:db8:85a3::8a2e:370:7334",
            "::1",
            "foo",
            "http://",
            "http://example",
            "http://example.",
            "http://.com",
            "http://invalid-.com",
            "http://-invalid.com",
            "http://inv-.alid-.com",
            "http://inv-.-alid.com",
            "foo bar baz",
            "foo \u2713",
            "http://@foo:bar@example.com",
            "http://:bar@example.com",
            "http://bar:bar:bar@example.com",
            "127.0",
        ],
    )
    def test_bad_value(self, value):
        with pytest.raises(ValueError):
            inputs.ipv4(value)

    def test_schema(self):
        assert inputs.ipv4.__schema__ == {"type": "string", "format": "ipv4"}


class IPv6Test(object):
    @pytest.mark.parametrize(
        "value",
        [
            "2001:db8:85a3::8a2e:370:7334",
            "::1",
        ],
    )
    def test_valid_value(self, value):
        assert inputs.ipv6(value) == value

    @pytest.mark.parametrize(
        "value",
        [
            "200.8.9.10",
            "127.0.0.1",
            "foo",
            "http://",
            "http://example",
            "http://example.",
            "http://.com",
            "http://invalid-.com",
            "http://-invalid.com",
            "http://inv-.alid-.com",
            "http://inv-.-alid.com",
            "foo bar baz",
            "foo \u2713",
            "http://@foo:bar@example.com",
            "http://:bar@example.com",
            "http://bar:bar:bar@example.com",
            "127.0",
        ],
    )
    def test_bad_value(self, value):
        with pytest.raises(ValueError):
            inputs.ipv6(value)

    def test_schema(self):
        assert inputs.ipv6.__schema__ == {"type": "string", "format": "ipv6"}


class EmailTest(object):
    def assert_bad_email(self, validator, value, msg=None):
        msg = msg or "{0} is not a valid email"
        with pytest.raises(ValueError) as cm:
            validator(value)
        assert str(cm.value) == msg.format(value)

    @pytest.mark.parametrize(
        "value",
        [
            "test@gmail.com",
            "coucou@cmoi.fr",
            "coucou+another@cmoi.fr",
            "Coucou@cmoi.fr",
            "me@valid-with-hyphens.com",
            "me@subdomain.example.com",
            "me@sub.subdomain.example.com",
            "Loïc.Accentué@voilà.fr",
        ],
    )
    def test_valid_value_default(self, value):
        validator = inputs.email()
        assert validator(value) == value

    @pytest.mark.parametrize(
        "value",
        [
            "me@localhost",
            "me@127.0.0.1",
            "me@127.1.2.3",
            "me@::1",
            "me@200.8.9.10",
            "me@2001:db8:85a3::8a2e:370:7334",
            "foo@bar.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?"
            + ".?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?@",
        ],
    )
    def test_invalid_value_default(self, value):
        self.assert_bad_email(inputs.email(), value)

    @pytest.mark.parametrize(
        "value",
        [
            "test@gmail.com",
            "test@live.com",
        ],
    )
    def test_valid_value_check(self, value):
        email = inputs.email(check=True)
        assert email(value) == value

    @pytest.mark.parametrize(
        "value",
        [
            "coucou@not-found.fr",
            "me@localhost",
            "me@127.0.0.1",
            "me@127.1.2.3",
            "me@::1",
            "me@200.8.9.10",
            "me@2001:db8:85a3::8a2e:370:7334",
        ],
    )
    def test_invalid_values_check(self, value):
        email = inputs.email(check=True)
        self.assert_bad_email(email, value)

    @pytest.mark.parametrize(
        "value",
        [
            "test@gmail.com",
            "coucou@cmoi.fr",
            "coucou+another@cmoi.fr",
            "Coucou@cmoi.fr",
            "me@valid-with-hyphens.com",
            "me@subdomain.example.com",
            "me@200.8.9.10",
            "me@2001:db8:85a3::8a2e:370:7334",
        ],
    )
    def test_valid_value_ip(self, value):
        email = inputs.email(ip=True)
        assert email(value) == value

    @pytest.mark.parametrize(
        "value",
        [
            "me@localhost",
            "me@127.0.0.1",
            "me@127.1.2.3",
            "me@::1",
        ],
    )
    def test_invalid_value_ip(self, value):
        email = inputs.email(ip=True)
        self.assert_bad_email(email, value)

    @pytest.mark.parametrize(
        "value",
        [
            "test@gmail.com",
            "coucou@cmoi.fr",
            "coucou+another@cmoi.fr",
            "Coucou@cmoi.fr",
            "coucou@localhost",
            "me@valid-with-hyphens.com",
            "me@subdomain.example.com",
            "me@localhost",
        ],
    )
    def test_valid_value_local(self, value):
        email = inputs.email(local=True)
        assert email(value) == value

    @pytest.mark.parametrize(
        "value",
        [
            "me@127.0.0.1",
            "me@127.1.2.3",
            "me@::1",
            "me@200.8.9.10",
            "me@2001:db8:85a3::8a2e:370:7334",
        ],
    )
    def test_invalid_value_local(self, value):
        email = inputs.email(local=True)
        self.assert_bad_email(email, value)

    @pytest.mark.parametrize(
        "value",
        [
            "test@gmail.com",
            "coucou@cmoi.fr",
            "coucou+another@cmoi.fr",
            "Coucou@cmoi.fr",
            "coucou@localhost",
            "me@valid-with-hyphens.com",
            "me@subdomain.example.com",
            "me@200.8.9.10",
            "me@2001:db8:85a3::8a2e:370:7334",
            "me@localhost",
            "me@127.0.0.1",
            "me@127.1.2.3",
            "me@::1",
        ],
    )
    def test_valid_value_ip_and_local(self, value):
        email = inputs.email(ip=True, local=True)
        assert email(value) == value

    @pytest.mark.parametrize(
        "value",
        [
            "test@gmail.com",
            "coucou@cmoi.fr",
            "coucou+another@cmoi.fr",
            "Coucou@cmoi.fr",
        ],
    )
    def test_valid_value_domains(self, value):
        email = inputs.email(domains=("gmail.com", "cmoi.fr"))
        assert email(value) == value

    @pytest.mark.parametrize(
        "value",
        [
            "me@valid-with-hyphens.com",
            "me@subdomain.example.com",
            "me@localhost",
            "me@127.0.0.1",
            "me@127.1.2.3",
            "me@::1",
            "me@200.8.9.10",
            "me@2001:db8:85a3::8a2e:370:7334",
        ],
    )
    def test_invalid_value_domains(self, value):
        email = inputs.email(domains=("gmail.com", "cmoi.fr"))
        self.assert_bad_email(
            email, value, "{0} does not belong to the authorized domains"
        )

    @pytest.mark.parametrize(
        "value",
        [
            "test@gmail.com",
            "coucou@cmoi.fr",
            "coucou+another@cmoi.fr",
            "Coucou@cmoi.fr",
        ],
    )
    def test_valid_value_exclude(self, value):
        email = inputs.email(exclude=("somewhere.com", "foo.bar"))
        assert email(value) == value

    @pytest.mark.parametrize(
        "value",
        [
            "me@somewhere.com",
            "me@foo.bar",
        ],
    )
    def test_invalid_value_exclude(self, value):
        email = inputs.email(exclude=("somewhere.com", "foo.bar"))
        self.assert_bad_email(email, value, "{0} belongs to a forbidden domain")

    @pytest.mark.parametrize(
        "value",
        [
            "someone@",
            "@somewhere",
            "email.somewhere.com",
            "[invalid!email]",
            "me.@somewhere",
            "me..something@somewhere",
        ],
    )
    def test_bad_email(self, value):
        email = inputs.email()
        self.assert_bad_email(email, value)

    def test_schema(self):
        assert inputs.email().__schema__ == {"type": "string", "format": "email"}


class RegexTest(object):
    @pytest.mark.parametrize(
        "value",
        [
            "123",
            "1234567890",
            "00000",
        ],
    )
    def test_valid_input(self, value):
        num_only = inputs.regex(r"^[0-9]+$")
        assert num_only(value) == value

    @pytest.mark.parametrize(
        "value",
        [
            "abc",
            "123abc",
            "abc123",
            "",
        ],
    )
    def test_bad_input(self, value):
        num_only = inputs.regex(r"^[0-9]+$")
        with pytest.raises(ValueError):
            num_only(value)

    def test_bad_pattern(self):
        with pytest.raises(re.error):
            inputs.regex("[")

    def test_schema(self):
        assert inputs.regex(r"^[0-9]+$").__schema__ == {
            "type": "string",
            "pattern": "^[0-9]+$",
        }


class BooleanTest(object):
    def test_false(self):
        assert inputs.boolean("False") is False

    def test_0(self):
        assert inputs.boolean("0") is False

    def test_true(self):
        assert inputs.boolean("true") is True

    def test_1(self):
        assert inputs.boolean("1") is True

    def test_case(self):
        assert inputs.boolean("FaLSE") is False
        assert inputs.boolean("FaLSE") is False

    def test_python_bool(self):
        assert inputs.boolean(True) is True
        assert inputs.boolean(False) is False

    def test_bad_boolean(self):
        with pytest.raises(ValueError):
            inputs.boolean("blah")
        with pytest.raises(ValueError):
            inputs.boolean(None)

    def test_checkbox(self):
        assert inputs.boolean("on") is True

    def test_non_strings(self):
        assert inputs.boolean(0) is False
        assert inputs.boolean(1) is True
        assert inputs.boolean([]) is False

    def test_schema(self):
        assert inputs.boolean.__schema__ == {"type": "boolean"}


class DateTest(object):
    def test_later_than_1900(self):
        assert inputs.date("1900-01-01") == datetime(1900, 1, 1)

    def test_error(self):
        with pytest.raises(ValueError):
            inputs.date("2008-13-13")

    def test_default(self):
        assert inputs.date("2008-08-01") == datetime(2008, 8, 1)

    def test_schema(self):
        assert inputs.date.__schema__ == {"type": "string", "format": "date"}


class NaturalTest(object):
    def test_negative(self):
        with pytest.raises(ValueError):
            inputs.natural(-1)

    def test_default(self):
        assert inputs.natural(3) == 3

    def test_string(self):
        with pytest.raises(ValueError):
            inputs.natural("foo")

    def test_schema(self):
        assert inputs.natural.__schema__ == {"type": "integer", "minimum": 0}


class PositiveTest(object):
    def test_positive(self):
        assert inputs.positive(1) == 1
        assert inputs.positive(10000) == 10000

    def test_zero(self):
        with pytest.raises(ValueError):
            inputs.positive(0)

    def test_negative(self):
        with pytest.raises(ValueError):
            inputs.positive(-1)

    def test_schema(self):
        assert inputs.positive.__schema__ == {
            "type": "integer",
            "minimum": 0,
            "exclusiveMinimum": True,
        }


class IntRangeTest(object):
    def test_valid_range(self):
        int_range = inputs.int_range(1, 5)
        assert int_range(3) == 3

    def test_inclusive_range(self):
        int_range = inputs.int_range(1, 5)
        assert int_range(5) == 5

    def test_lower(self):
        int_range = inputs.int_range(0, 5)
        with pytest.raises(ValueError):
            int_range(-1)

    def test_higher(self):
        int_range = inputs.int_range(0, 5)
        with pytest.raises(ValueError):
            int_range(6)

    def test_schema(self):
        assert inputs.int_range(1, 5).__schema__ == {
            "type": "integer",
            "minimum": 1,
            "maximum": 5,
        }


interval_test_values = [
    (
        # Full precision with explicit UTC.
        "2013-01-01T12:30:00Z/P1Y2M3DT4H5M6S",
        (
            datetime(2013, 1, 1, 12, 30, 0, tzinfo=pytz.utc),
            datetime(2014, 3, 5, 16, 35, 6, tzinfo=pytz.utc),
        ),
    ),
    (
        # Full precision with alternate UTC indication
        "2013-01-01T12:30+00:00/P2D",
        (
            datetime(2013, 1, 1, 12, 30, 0, tzinfo=pytz.utc),
            datetime(2013, 1, 3, 12, 30, 0, tzinfo=pytz.utc),
        ),
    ),
    (
        # Implicit UTC with time
        "2013-01-01T15:00/P1M",
        (
            datetime(2013, 1, 1, 15, 0, 0, tzinfo=pytz.utc),
            datetime(2013, 1, 31, 15, 0, 0, tzinfo=pytz.utc),
        ),
    ),
    (
        # TZ conversion
        "2013-01-01T17:00-05:00/P2W",
        (
            datetime(2013, 1, 1, 22, 0, 0, tzinfo=pytz.utc),
            datetime(2013, 1, 15, 22, 0, 0, tzinfo=pytz.utc),
        ),
    ),
    (
        # Date upgrade to midnight-midnight period
        "2013-01-01/P3D",
        (
            datetime(2013, 1, 1, 0, 0, 0, tzinfo=pytz.utc),
            datetime(2013, 1, 4, 0, 0, 0, 0, tzinfo=pytz.utc),
        ),
    ),
    (
        # Start/end with UTC
        "2013-01-01T12:00:00Z/2013-02-01T12:00:00Z",
        (
            datetime(2013, 1, 1, 12, 0, 0, tzinfo=pytz.utc),
            datetime(2013, 2, 1, 12, 0, 0, tzinfo=pytz.utc),
        ),
    ),
    (
        # Start/end with time upgrade
        "2013-01-01/2013-06-30",
        (
            datetime(2013, 1, 1, tzinfo=pytz.utc),
            datetime(2013, 6, 30, tzinfo=pytz.utc),
        ),
    ),
    (
        # Start/end with TZ conversion
        "2013-02-17T12:00:00-07:00/2013-02-28T15:00:00-07:00",
        (
            datetime(2013, 2, 17, 19, 0, 0, tzinfo=pytz.utc),
            datetime(2013, 2, 28, 22, 0, 0, tzinfo=pytz.utc),
        ),
    ),
    (  # Resolution expansion for single date(time)
        # Second with UTC
        "2013-01-01T12:30:45Z",
        (
            datetime(2013, 1, 1, 12, 30, 45, tzinfo=pytz.utc),
            datetime(2013, 1, 1, 12, 30, 46, tzinfo=pytz.utc),
        ),
    ),
    (
        # Second with tz conversion
        "2013-01-01T12:30:45+02:00",
        (
            datetime(2013, 1, 1, 10, 30, 45, tzinfo=pytz.utc),
            datetime(2013, 1, 1, 10, 30, 46, tzinfo=pytz.utc),
        ),
    ),
    (
        # Second with implicit UTC
        "2013-01-01T12:30:45",
        (
            datetime(2013, 1, 1, 12, 30, 45, tzinfo=pytz.utc),
            datetime(2013, 1, 1, 12, 30, 46, tzinfo=pytz.utc),
        ),
    ),
    (
        # Minute with UTC
        "2013-01-01T12:30+00:00",
        (
            datetime(2013, 1, 1, 12, 30, tzinfo=pytz.utc),
            datetime(2013, 1, 1, 12, 31, tzinfo=pytz.utc),
        ),
    ),
    (
        # Minute with conversion
        "2013-01-01T12:30+04:00",
        (
            datetime(2013, 1, 1, 8, 30, tzinfo=pytz.utc),
            datetime(2013, 1, 1, 8, 31, tzinfo=pytz.utc),
        ),
    ),
    (
        # Minute with implicit UTC
        "2013-01-01T12:30",
        (
            datetime(2013, 1, 1, 12, 30, tzinfo=pytz.utc),
            datetime(2013, 1, 1, 12, 31, tzinfo=pytz.utc),
        ),
    ),
    (
        # Hour, explicit UTC
        "2013-01-01T12Z",
        (
            datetime(2013, 1, 1, 12, tzinfo=pytz.utc),
            datetime(2013, 1, 1, 13, tzinfo=pytz.utc),
        ),
    ),
    (
        # Hour with offset
        "2013-01-01T12-07:00",
        (
            datetime(2013, 1, 1, 19, tzinfo=pytz.utc),
            datetime(2013, 1, 1, 20, tzinfo=pytz.utc),
        ),
    ),
    (
        # Hour with implicit UTC
        "2013-01-01T12",
        (
            datetime(2013, 1, 1, 12, tzinfo=pytz.utc),
            datetime(2013, 1, 1, 13, tzinfo=pytz.utc),
        ),
    ),
    (
        # Interval with trailing zero fractional seconds should
        # be accepted.
        "2013-01-01T12:00:00.0/2013-01-01T12:30:00.000000",
        (
            datetime(2013, 1, 1, 12, tzinfo=pytz.utc),
            datetime(2013, 1, 1, 12, 30, tzinfo=pytz.utc),
        ),
    ),
]


class IsoIntervalTest(object):
    @pytest.mark.parametrize("value,expected", interval_test_values)
    def test_valid_value(self, value, expected):
        assert inputs.iso8601interval(value) == expected

    def test_error_message(self):
        with pytest.raises(ValueError) as cm:
            inputs.iso8601interval("2013-01-01/blah")
        expected = "Invalid argument: 2013-01-01/blah. argument must be a valid ISO8601 date/time interval."
        assert str(cm.value) == expected

    @pytest.mark.parametrize(
        "value",
        [
            "2013-01T14:",
            "",
            "asdf",
            "01/01/2013",
        ],
    )
    def test_bad_values(self, value):
        with pytest.raises(ValueError):
            inputs.iso8601interval(value)

    def test_schema(self):
        assert inputs.iso8601interval.__schema__ == {
            "type": "string",
            "format": "iso8601-interval",
        }
