from django.test import RequestFactory, TestCase, override_settings
from django.utils.encoding import force_bytes

from wagtail.models import Site

from ..utils import (
    FieldsParameterParseError,
    get_base_url,
    parse_boolean,
    parse_fields_parameter,
)


class DynamicBaseUrl:
    def __str__(self):
        return "https://www.example.com"

    def __bytes__(self):
        return force_bytes(self.__str__())

    def decode(self, *args, **kwargs):
        return self.__bytes__().decode(*args, **kwargs)


class TestGetBaseUrl(TestCase):
    def setUp(self):
        Site.objects.all().delete()

    def prepare_site(self):
        return Site.objects.get_or_create(
            hostname="other.example.com",
            port=8080,
            root_page_id=1,
            is_default_site=True,
        )[0]

    def clear_cached_site(self, request):
        del request._wagtail_site

    def test_get_base_url_unset(self):
        self.assertIsNone(get_base_url())

    def test_get_base_url_from_request(self):
        # base url for siteless request should be None
        request = RequestFactory().get("/")
        self.assertIsNone(Site.find_for_request(request))
        self.assertIsNone(get_base_url(request))

        # base url for request with a site should be based on the site's details
        site = self.prepare_site()
        self.clear_cached_site(request)
        self.assertEqual(site, Site.find_for_request(request))
        self.assertEqual(get_base_url(request), "http://other.example.com:8080")

        # port 443 should indicate https without a port
        site.port = 443
        site.save()
        self.clear_cached_site(request)
        self.assertEqual(get_base_url(request), "https://other.example.com")

        # port 80 should indicate http without a port
        site.port = 80
        site.save()
        self.clear_cached_site(request)
        self.assertEqual(get_base_url(request), "http://other.example.com")

    @override_settings(WAGTAILAPI_BASE_URL="https://bar.example.com")
    def test_get_base_url_prefers_setting(self):
        request = RequestFactory().get("/")
        site = self.prepare_site()
        self.assertEqual(site, Site.find_for_request(request))
        self.assertEqual(get_base_url(request), "https://bar.example.com")
        with override_settings(WAGTAILAPI_BASE_URL=None):
            self.assertEqual(get_base_url(request), "http://other.example.com:8080")

    @override_settings(WAGTAILAPI_BASE_URL="https://bar.example.com")
    def test_get_base_url_from_setting_string(self):
        self.assertEqual(get_base_url(), "https://bar.example.com")

    @override_settings(WAGTAILAPI_BASE_URL=b"https://baz.example.com")
    def test_get_base_url_from_setting_bytes(self):
        self.assertEqual(get_base_url(), "https://baz.example.com")

    @override_settings(WAGTAILAPI_BASE_URL=DynamicBaseUrl())
    def test_get_base_url_from_setting_object(self):
        self.assertEqual(get_base_url(), "https://www.example.com")


class TestParseFieldsParameter(TestCase):
    # GOOD STUFF

    def test_valid_single_field(self):
        parsed = parse_fields_parameter("test")

        self.assertEqual(
            parsed,
            [
                ("test", False, None),
            ],
        )

    def test_valid_multiple_fields(self):
        parsed = parse_fields_parameter("test,another_test")

        self.assertEqual(
            parsed,
            [
                ("test", False, None),
                ("another_test", False, None),
            ],
        )

    def test_valid_negated_field(self):
        parsed = parse_fields_parameter("-test")

        self.assertEqual(
            parsed,
            [
                ("test", True, None),
            ],
        )

    def test_valid_nested_fields(self):
        parsed = parse_fields_parameter("test(foo,bar)")

        self.assertEqual(
            parsed,
            [
                (
                    "test",
                    False,
                    [
                        ("foo", False, None),
                        ("bar", False, None),
                    ],
                ),
            ],
        )

    def test_valid_star_field(self):
        parsed = parse_fields_parameter("*,-test")

        self.assertEqual(
            parsed,
            [
                ("*", False, None),
                ("test", True, None),
            ],
        )

    def test_valid_star_with_additional_field(self):
        # Note: '*,test' is not allowed but '*,test(foo)' is
        parsed = parse_fields_parameter("*,test(foo)")

        self.assertEqual(
            parsed,
            [
                ("*", False, None),
                (
                    "test",
                    False,
                    [
                        ("foo", False, None),
                    ],
                ),
            ],
        )

    def test_valid_underscore_field(self):
        parsed = parse_fields_parameter("_,test")

        self.assertEqual(
            parsed,
            [
                ("_", False, None),
                ("test", False, None),
            ],
        )

    def test_valid_field_with_underscore_in_middle(self):
        parsed = parse_fields_parameter("a_test")

        self.assertEqual(
            parsed,
            [
                ("a_test", False, None),
            ],
        )

    def test_valid_negated_field_with_underscore_in_middle(self):
        parsed = parse_fields_parameter("-a_test")

        self.assertEqual(
            parsed,
            [
                ("a_test", True, None),
            ],
        )

    def test_valid_field_with_underscore_at_beginning(self):
        parsed = parse_fields_parameter("_test")

        self.assertEqual(
            parsed,
            [
                ("_test", False, None),
            ],
        )

    def test_valid_field_with_underscore_at_end(self):
        parsed = parse_fields_parameter("test_")

        self.assertEqual(
            parsed,
            [
                ("test_", False, None),
            ],
        )

    # BAD STUFF

    def test_invalid_char(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("test#")

        self.assertEqual(str(e.exception), "unexpected char '#' at position 4")

    def test_invalid_whitespace_before_identifier(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter(" test")

        self.assertEqual(str(e.exception), "unexpected whitespace at position 0")

    def test_invalid_whitespace_after_identifier(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("test ")

        self.assertEqual(str(e.exception), "unexpected whitespace at position 4")

    def test_invalid_whitespace_after_comma(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("test, test")

        self.assertEqual(str(e.exception), "unexpected whitespace at position 5")

    def test_invalid_whitespace_before_comma(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("test ,test")

        self.assertEqual(str(e.exception), "unexpected whitespace at position 4")

    def test_invalid_unexpected_negation_operator(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("test-")

        self.assertEqual(str(e.exception), "unexpected char '-' at position 4")

    def test_invalid_unexpected_open_bracket(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("test,(foo)")

        self.assertEqual(str(e.exception), "unexpected char '(' at position 5")

    def test_invalid_unexpected_close_bracket(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("test)")

        self.assertEqual(str(e.exception), "unexpected char ')' at position 4")

    def test_invalid_unexpected_comma_in_middle(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("test,,foo")

        self.assertEqual(str(e.exception), "unexpected char ',' at position 5")

    def test_invalid_unexpected_comma_at_end(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("test,foo,")

        self.assertEqual(str(e.exception), "unexpected char ',' at position 9")

    def test_invalid_unclosed_bracket(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("test(foo")

        self.assertEqual(
            str(e.exception),
            "unexpected end of input (did you miss out a close bracket?)",
        )

    def test_invalid_subfields_on_negated_field(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("-test(foo)")

        self.assertEqual(str(e.exception), "unexpected char '(' at position 5")

    def test_invalid_star_field_in_wrong_position(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("test,*")

        self.assertEqual(str(e.exception), "'*' must be in the first position")

    def test_invalid_negated_star(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("-*")

        self.assertEqual(str(e.exception), "'*' cannot be negated")

    def test_invalid_star_with_nesting(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("*(foo,bar)")

        self.assertEqual(str(e.exception), "unexpected char '(' at position 1")

    def test_invalid_star_with_chars_after(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("*foo")

        self.assertEqual(str(e.exception), "unexpected char 'f' at position 1")

    def test_invalid_star_with_chars_before(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("foo*")

        self.assertEqual(str(e.exception), "unexpected char '*' at position 3")

    def test_invalid_star_with_additional_field(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("*,foo")

        self.assertEqual(
            str(e.exception), "additional fields with '*' doesn't make sense"
        )

    def test_invalid_underscore_in_wrong_position(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("test,_")

        self.assertEqual(str(e.exception), "'_' must be in the first position")

    def test_invalid_negated_underscore(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("-_")

        self.assertEqual(str(e.exception), "'_' cannot be negated")

    def test_invalid_underscore_with_nesting(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("_(foo,bar)")

        self.assertEqual(str(e.exception), "unexpected char '(' at position 1")

    def test_invalid_underscore_with_negated_field(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("_,-foo")

        self.assertEqual(str(e.exception), "negated fields with '_' doesn't make sense")

    def test_invalid_star_and_underscore(self):
        with self.assertRaises(FieldsParameterParseError) as e:
            parse_fields_parameter("*,_")

        self.assertEqual(str(e.exception), "'_' must be in the first position")


class TestParseBoolean(TestCase):
    # GOOD STUFF

    def test_valid_true(self):
        parsed = parse_boolean("true")

        self.assertIs(parsed, True)

    def test_valid_false(self):
        parsed = parse_boolean("false")

        self.assertIs(parsed, False)

    def test_valid_1(self):
        parsed = parse_boolean("1")

        self.assertIs(parsed, True)

    def test_valid_0(self):
        parsed = parse_boolean("0")

        self.assertIs(parsed, False)

    # BAD STUFF

    def test_invalid(self):
        with self.assertRaises(ValueError) as e:
            parse_boolean("foo")

        self.assertEqual(str(e.exception), "expected 'true' or 'false', got 'foo'")

    def test_invalid_integer(self):
        with self.assertRaises(ValueError) as e:
            parse_boolean("2")

        self.assertEqual(str(e.exception), "expected 'true' or 'false', got '2'")
