import webtest
from webtest.debugapp import debug_app
from webob import Request
from webob.response import gzip_app_iter

from tests.compat import unittest

import webbrowser


def links_app(environ, start_response):
    req = Request(environ)
    status = "200 OK"
    responses = {
        '/': """
            <html>
                <head><title>page with links</title></head>
                <body>
                    <a href="/foo/">Foo</a>
                    <a href='bar'>Bar</a>
                    <a href='baz/' id='id_baz'>Baz</a>
                    <a href='#' id='fake_baz'>Baz</a>
                    <a href='javascript:alert("123")' id='js_baz'>Baz</a>
                    <script>
                        var link = "<a href='/boo/'>Boo</a>";
                    </script>
                    <a href='/spam/'>Click me!</a>
                    <a href='/egg/'>Click me!</a>
                    <button
                        id="button1"
                        onclick="location.href='/foo/'"
                        >Button</button>
                    <button
                        id="button2">Button</button>
                    <button
                        id="button3"
                        onclick="lomistakecation.href='/foo/'"
                        >Button</button>
                </body>
            </html>
            """,

        '/foo/': (
            '<html><body>This is foo. <a href="bar">Bar</a> '
            '</body></html>'
        ),
        '/foo/bar': '<html><body>This is foobar.</body></html>',
        '/bar': '<html><body>This is bar.</body></html>',
        '/baz/': '<html><body>This is baz.</body></html>',
        '/spam/': '<html><body>This is spam.</body></html>',
        '/egg/': '<html><body>Just eggs.</body></html>',

        '/utf8/': """
            <html>
                <head><title>Тестовая страница</title></head>
                <body>
                    <a href='/foo/'>Менделеев</a>
                    <a href='/baz/' title='Поэт'>Пушкин</a>
                    <img src='/egg/' title='Поэт'>
                    <script>
                        var link = "<a href='/boo/'>Злодейская ссылка</a>";
                    </script>
                </body>
            </html>
        """,
        '/no_form/': """
            <html>
                <head><title>Page without form</title></head>
                <body>
                    <h1>This is not the form you are looking for</h1>
                </body>
            </html>
        """,
        '/one_forms/': """
            <html>
                <head><title>Page without form</title></head>
                <body>
                    <form method="POST" id="first_form"></form>
                </body>
            </html>
        """,
        '/many_forms/': """
            <html>
                <head><title>Page without form</title></head>
                <body>
                    <form method="POST" id="first_form"></form>
                    <form method="POST" id="second_form"></form>
                </body>
            </html>
        """,
        '/html_in_anchor/': """
            <html>
                <head><title>Page with HTML in an anchor tag</title></head>
                <body>
                    <a href='/foo/'>Foo Bar<span class='baz qux'>Quz</span></a>
                </body>
            </html>
        """,
        '/json/': '{"foo": "bar"}',
    }

    utf8_paths = ['/utf8/']
    body = responses[req.path_info]
    body = body.encode('utf8')
    headers = [
        ('Content-Type', 'text/html'),
        ('Content-Length', str(len(body)))
    ]
    if req.path_info in utf8_paths:
        headers[0] = ('Content-Type', 'text/html; charset=utf-8')
    # PEP 3333 requires native strings:
    headers = [(str(k), str(v)) for k, v in headers]
    start_response(str(status), headers)
    return [body]


def svg_application(env, start_response):
    start_response('200 OK', [('Content-Type', 'image/svg+xml')])
    return [
        b"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>"""
        b"""<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" """
        b""" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">"""
        b"""<svg xmlns="http://www.w3.org/2000/svg" """
        b"""xmlns:xlink="http://www.w3.org/1999/xlink"></svg>"""
    ]


def gzipped_app(environ, start_response):
    status = "200 OK"
    encoded_body = list(gzip_app_iter([b'test']))
    headers = [
        ('Content-Type', 'text/html'),
        ('Content-Encoding', 'gzip'),
    ]
    # PEP 3333 requires native strings:
    headers = [(str(k), str(v)) for k, v in headers]
    start_response(str(status), headers)
    return encoded_body


class TestResponse(unittest.TestCase):
    def test_repr(self):
        def _repr(v):
            br = repr(v)
            if len(br) > 18:
                br = br[:10] + '...' + br[-5:]
                br += '/%s' % len(v)

            return br

        app = webtest.TestApp(debug_app)
        res = app.post('/')
        self.assertEqual(
            repr(res),
            '<200 OK text/plain body=%s>' % _repr(res.body)
        )
        res.content_type = None
        self.assertEqual(
            repr(res),
            '<200 OK body=%s>' % _repr(res.body)
        )
        res.location = 'http://pylons.org'
        self.assertEqual(
            repr(res),
            '<200 OK location: http://pylons.org body=%s>' % _repr(res.body)
        )

        res.body = b''
        self.assertEqual(
            repr(res),
            '<200 OK location: http://pylons.org no body>'
        )

    def test_mustcontains(self):
        app = webtest.TestApp(debug_app)
        res = app.post('/', params='foobar')
        res.mustcontain('foobar')
        self.assertRaises(IndexError, res.mustcontain, 'not found')
        res.mustcontain('foobar', no='not found')
        res.mustcontain('foobar', no=['not found', 'not found either'])
        self.assertRaises(IndexError, res.mustcontain, no='foobar')
        self.assertRaises(
            TypeError,
            res.mustcontain, invalid_param='foobar'
        )

    def test_click(self):
        app = webtest.TestApp(links_app)
        self.assertIn('This is foo.', app.get('/').click('Foo'))
        self.assertIn(
            'This is foobar.',
            app.get('/').click('Foo').click('Bar')
        )
        self.assertIn('This is bar.', app.get('/').click('Bar'))
        # should skip non-clickable links
        self.assertIn(
            'This is baz.',
            app.get('/').click('Baz')
        )
        self.assertIn('This is baz.', app.get('/').click(linkid='id_baz'))
        self.assertIn('This is baz.', app.get('/').click(href='baz/'))
        self.assertIn(
            'This is spam.',
            app.get('/').click('Click me!', index=0)
        )
        self.assertIn(
            'Just eggs.',
            app.get('/').click('Click me!', index=1)
        )
        self.assertIn(
            'This is foo.',
            app.get('/html_in_anchor/').click('baz qux')
        )

        def dont_match_anchor_tag():
            app.get('/html_in_anchor/').click('href')
        self.assertRaises(IndexError, dont_match_anchor_tag)

        def multiple_links():
            app.get('/').click('Click me!')
        self.assertRaises(IndexError, multiple_links)

        def invalid_index():
            app.get('/').click('Click me!', index=2)
        self.assertRaises(IndexError, invalid_index)

        def no_links_found():
            app.get('/').click('Ham')
        self.assertRaises(IndexError, no_links_found)

        def tag_inside_script():
            app.get('/').click('Boo')
        self.assertRaises(IndexError, tag_inside_script)

    def test_click_utf8(self):
        app = webtest.TestApp(links_app, use_unicode=False)
        resp = app.get('/utf8/')
        self.assertEqual(resp.charset, 'utf-8')

    def test_click_u(self):
        app = webtest.TestApp(links_app)
        resp = app.get('/utf8/')

        self.assertIn("Тестовая страница", resp)
        self.assertIn('This is foo.', resp.click('Менделеев'))

    def test_clickbutton(self):
        app = webtest.TestApp(links_app)
        self.assertIn(
            'This is foo.',
            app.get('/').clickbutton(buttonid='button1', verbose=True)
        )
        self.assertIn(
            'This is foo.',
            app.get('/').clickbutton(buttonid='button3', onclick=r".*href='(.*?)'", verbose=True)
        )
        self.assertRaises(
            IndexError,
            app.get('/').clickbutton, buttonid='button2'
        )
        self.assertRaises(
            IndexError,
            app.get('/').clickbutton, buttonid='button3'
        )

    def test_referer(self):
        app = webtest.TestApp(links_app)
        resp = app.get('/').click('Foo')
        self.assertIn('Referer', resp.request.headers)
        self.assertEqual(resp.request.headers['Referer'], 'http://localhost/')

        resp = app.get('/').clickbutton(buttonid='button1')
        self.assertIn('Referer', resp.request.headers)
        self.assertEqual(resp.request.headers['Referer'], 'http://localhost/')

        resp = app.get('/one_forms/').form.submit()
        self.assertIn('Referer', resp.request.headers)
        self.assertEqual(resp.request.headers['Referer'],
                         'http://localhost/one_forms/')

    def test_xml_attribute(self):
        app = webtest.TestApp(links_app)

        resp = app.get('/no_form/')
        self.assertRaises(
            AttributeError,
            getattr,
            resp, 'xml'
        )

        resp.content_type = 'text/xml'
        resp.xml

    def test_lxml_attribute(self):
        app = webtest.TestApp(links_app)
        resp = app.post('/')
        resp.content_type = 'text/xml'
        print(resp.body)
        print(resp.lxml)

    def test_lxml_attribute_with_encoding_declaration(self):
        app = webtest.TestApp(svg_application)
        resp = app.get('/')
        print(resp.body)
        print(resp.lxml)

    def test_pyquery(self):
        app = webtest.TestApp(svg_application)
        resp = app.get('/')
        self.assertRaises(ValueError, lambda: resp.pyquery)
        pq = resp.PyQuery(parser='xml', remove_namespaces=True)
        assert len(pq('svg')) == 1
        pq = resp.PyQuery(parser='xml')
        assert len(pq('svg')) == 0

    def test_html_attribute(self):
        app = webtest.TestApp(links_app)
        res = app.post('/')
        res.content_type = 'text/plain'
        self.assertRaises(
            AttributeError,
            getattr, res, 'html'
        )

    def test_no_form(self):
        app = webtest.TestApp(links_app)

        resp = app.get('/no_form/')
        self.assertRaises(
            TypeError,
            getattr,
            resp, 'form'
        )

    def test_one_forms(self):
        app = webtest.TestApp(links_app)

        resp = app.get('/one_forms/')
        self.assertEqual(resp.form.id, 'first_form')

    def test_too_many_forms(self):
        app = webtest.TestApp(links_app)

        resp = app.get('/many_forms/')
        self.assertRaises(
            TypeError,
            getattr,
            resp, 'form'
        )

    def test_showbrowser(self):
        def open_new(f):
            self.filename = f

        webbrowser.open_new = open_new
        app = webtest.TestApp(debug_app)
        res = app.post('/')
        res.showbrowser()

    def test_unicode_normal_body(self):
        app = webtest.TestApp(debug_app)
        res = app.post('/')
        self.assertRaises(
            AttributeError,
            getattr, res, 'unicode_normal_body'
        )
        res.charset = 'latin1'
        res.body = 'été'.encode('latin1')
        self.assertEqual(res.unicode_normal_body, 'été')

    def test_testbody(self):
        app = webtest.TestApp(debug_app)
        res = app.post('/')
        res.charset = 'utf8'
        res.body = 'été'.encode('latin1')
        res.testbody

    def test_xml(self):
        app = webtest.TestApp(links_app)

        resp = app.get('/no_form/')
        self.assertRaises(
            AttributeError,
            getattr,
            resp, 'xml'
        )

        resp.content_type = 'text/xml'
        resp.xml

    def test_json(self):
        app = webtest.TestApp(links_app)

        resp = app.get('/json/')
        with self.assertRaises(AttributeError):
            resp.json

        resp.content_type = 'text/json'
        self.assertIn('foo', resp.json)

        resp.content_type = 'application/json'
        self.assertIn('foo', resp.json)

        resp.content_type = 'application/vnd.webtest+json'
        self.assertIn('foo', resp.json)

    def test_unicode(self):
        app = webtest.TestApp(links_app)

        resp = app.get('/')

        print(resp.__unicode__())

    def test_content_dezips(self):
        app = webtest.TestApp(gzipped_app)
        resp = app.get('/')
        self.assertEqual(resp.body, b'test')


class TestFollow(unittest.TestCase):

    def get_redirects_app(self, count=1, locations=None):
        """Return an app that issues a redirect ``count`` times"""

        remaining_redirects = [count]  # this means "nonlocal"
        if locations is None:
            locations = ['/'] * count

        def app(environ, start_response):
            headers = [('Content-Type', 'text/html')]

            if remaining_redirects[0] == 0:
                status = "200 OK"
                body = b"done"
            else:
                status = "302 Found"
                body = b''
                nextloc = str(locations.pop(0))
                headers.append(('location', nextloc))
                remaining_redirects[0] -= 1

            headers.append(('Content-Length', str(len(body))))
            # PEP 3333 requires native strings:
            headers = [(str(k), str(v)) for k, v in headers]
            start_response(str(status), headers)
            return [body]

        return webtest.TestApp(app)

    def test_follow_with_cookie(self):
        app = webtest.TestApp(debug_app)
        app.get('/?header-set-cookie=foo=bar')
        self.assertEqual(app.cookies['foo'], 'bar')
        resp = app.get('/?status=302%20Found&header-location=/')
        resp = resp.follow()
        resp.mustcontain('HTTP_COOKIE: foo=bar')

    def test_follow(self):
        app = self.get_redirects_app(1)
        resp = app.get('/')
        self.assertEqual(resp.status_int, 302)

        resp = resp.follow()
        self.assertEqual(resp.body, b'done')

        # can't follow non-redirect
        self.assertRaises(AssertionError, resp.follow)

    def test_follow_relative(self):
        app = self.get_redirects_app(2, ['hello/foo/', 'bar'])
        resp = app.get('/')
        self.assertEqual(resp.status_int, 302)
        resp = resp.follow()
        self.assertEqual(resp.status_int, 302)
        resp = resp.follow()
        self.assertEqual(resp.body, b'done')
        self.assertEqual(resp.request.url, 'http://localhost/hello/foo/bar')

    def test_follow_twice(self):
        app = self.get_redirects_app(2)
        resp = app.get('/').follow()
        self.assertEqual(resp.status_int, 302)
        resp = resp.follow()
        self.assertEqual(resp.status_int, 200)

    def test_maybe_follow_200(self):
        app = self.get_redirects_app(0)
        resp = app.get('/').maybe_follow()
        self.assertEqual(resp.body, b'done')

    def test_maybe_follow_once(self):
        app = self.get_redirects_app(1)
        resp = app.get('/').maybe_follow()
        self.assertEqual(resp.body, b'done')

    def test_maybe_follow_twice(self):
        app = self.get_redirects_app(2)
        resp = app.get('/').maybe_follow()
        self.assertEqual(resp.body, b'done')

    def test_maybe_follow_infinite(self):
        app = self.get_redirects_app(100000)
        self.assertRaises(AssertionError, app.get('/').maybe_follow)

    def test_pytest_collection_disabled(self):
        self.assertFalse(webtest.TestResponse.__test__)
