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

import re
import shutil

from bs4 import BeautifulSoup
import pytest

from quixote import cleanup
from quixote.http_request import Upload
from wcs.qommon.http_request import HTTPRequest
from wcs.qommon import sessions
from wcs import fields
from wcs.qommon.form import Form
from wcs.formdef import FormDef

from utilities import create_temporary_pub, MockSubstitutionVariables

def setup_module(module):
    cleanup()
    global pub
    pub = create_temporary_pub()
    req = HTTPRequest(None, {'SERVER_NAME': 'example.net',  'SCRIPT_NAME': ''})
    pub._set_request(req)
    req.session = sessions.Session(id=1)

def teardown_module(module):
    global pub
    shutil.rmtree(pub.APP_DIR)

def test_fill_admin_form():
    for klass in fields.field_classes:
        form = Form(use_tokens=False)
        klass().fill_admin_form(form)

def test_get_admin_attributes():
    for klass in fields.field_classes:
        klass().get_admin_attributes()

def test_add_to_form():
    for klass in fields.field_classes:
        form = Form(use_tokens=False)
        if klass is fields.PageField:
            with pytest.raises(AttributeError):
                klass(label='foo').add_to_form(form)
        else:
            klass(label='foo').add_to_form(form)

def test_string():
    # sample string
    assert fields.StringField().get_view_value('foo') == 'foo'
    assert fields.StringField().get_view_short_value('foo') == 'foo'
    assert fields.StringField().get_rst_view_value('foo') == 'foo'
    assert fields.StringField().get_csv_value('foo') == ['foo']
    # empty string
    assert fields.StringField().get_view_value('') == ''
    assert fields.StringField().get_view_short_value('') == ''
    assert fields.StringField().get_rst_view_value('') == ''
    assert fields.StringField().get_csv_value('') == ['']
    # url
    url = 'https://www.example.org/plop-plop-plop'
    assert str(fields.StringField().get_view_value(url)
            ) == '<a href="https://www.example.org/plop-plop-plop" rel="nofollow">https://www.example.org/plop-plop-plop</a>'
    assert str(fields.StringField().get_view_short_value(url)
            ) == '<a href="https://www.example.org/plop-plop-plop" rel="nofollow">https://www.example.org/plop-plop-plop</a>'
    assert str(fields.StringField().get_rst_view_value(url)) == url
    assert fields.StringField().get_csv_value(url) == [url]
    # hackish url
    url = 'https://www.example.org/"><script>plop</script>'
    assert str(fields.StringField().get_view_value(url)
            ) == '<a href="https://www.example.org/" rel="nofollow">https://www.example.org/</a>&quot;&gt;&lt;script&gt;plop&lt;/script&gt;'
    assert str(fields.StringField().get_view_short_value(url)
            ) == '<a href="https://www.example.org/" rel="nofollow">https://www.example.org/</a>&quot;&gt;&lt;script&gt;plop&lt;/script&gt;'
    assert str(fields.StringField().get_rst_view_value(url)) == url
    assert fields.StringField().get_csv_value(url) == [url]

def test_text():
    assert fields.TextField().get_view_short_value('foo'*10) == ('foo'*10)[:25] + ' (...)'
    assert fields.TextField().get_view_value('foo') == '<p>foo</p>'
    assert fields.TextField().get_view_value('foo\n\nfoo') == '<p>foo\n</p><p>\nfoo</p>'
    assert fields.TextField(pre=True).get_view_value('foo') == '<pre>foo</pre>'

    form = Form(use_tokens=False)
    fields.TextField().add_to_form(form)
    assert '<textarea' in str(form.render())
    assert 'cols="72"' in str(form.render())
    assert 'rows="5"' in str(form.render())

    form = Form(use_tokens=False)
    fields.TextField(cols='12', rows='12').add_to_form(form)
    assert '<textarea' in str(form.render())
    assert 'cols="12"' in str(form.render())
    assert 'rows="12"' in str(form.render())

def test_email():
    assert fields.EmailField().get_view_value('foo@localhost') == \
            '<a href="mailto:foo@localhost">foo@localhost</a>'
    assert fields.EmailField().get_rst_view_value('foo@localhost') == 'foo@localhost'

def test_bool():
    assert fields.BoolField().get_view_value(True) == 'Yes'
    assert fields.BoolField().get_view_value(False) == 'No'

def test_bool_stats():
    formdef = FormDef()
    formdef.url_name = 'title'
    formdef.fields = [fields.BoolField(id='1')]
    data_class = formdef.data_class()
    formdatas = []
    for value in (True, True, True, False):
        formdata = data_class()
        formdata.data = {'1': value}
        formdatas.append(formdata)
    stats = formdef.fields[0].stats(formdatas)
    assert re.findall('Yes.*75.*No.*25', str(stats))

def test_items():
    assert fields.ItemsField(items=['a', 'b', 'c']).get_view_value(['a', 'b']) == 'a, b'
    assert fields.ItemsField(items=['a', 'b', 'c']).get_csv_value(['a', 'b']) == ['a', 'b', '']
    assert len(fields.ItemsField(items=['a', 'b', 'c']).get_csv_heading()) == 3
    assert fields.ItemsField(items=['a', 'b', 'c'], max_choices=2).get_csv_value(['a', 'b']) == ['a', 'b']
    assert len(fields.ItemsField(items=['a', 'b', 'c'], max_choices=2).get_csv_heading()) == 2

    field = fields.ItemsField()
    field.data_source = {
        'type': 'formula',
        'value': '''[('1', 'foo'), ('2', 'bar')]'''
    }
    assert field.get_options() == [('1', 'foo', '1'), ('2', 'bar', '2')]
    assert field.get_options() == [('1', 'foo', '1'), ('2', 'bar', '2')] # twice for cached behaviour

def test_password():
    assert fields.PasswordField().get_view_value('xxx') == '●'*8

def test_file():
    upload = Upload('/foo/bar', content_type='text/plain')
    assert fields.FileField().get_csv_value(upload) == ['/foo/bar']

def test_page():
    formdef = FormDef()
    formdef.fields = []
    page = fields.PageField()
    assert page.is_visible({}, formdef) is True
    page.condition = {'type': 'python', 'value': 'var_foo == "bar"'}
    assert page.is_visible({}, formdef) is True # 'var_foo' is not defined
    formdef.fields = [fields.StringField(id='1', label='string', varname='foo')]
    assert page.is_visible({}, formdef) is False
    assert page.is_visible({'1': 'bar'}, formdef) is True
    assert page.is_visible({'1': 'baz'}, formdef) is False

def test_table():
    assert 'prefill' not in fields.TableField().get_admin_attributes()

def test_title():
    field = fields.TitleField(label='Foobar')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<h3 data-field-id="None">Foobar</h3>' in str(form.render())

    field = fields.TitleField(label='Foobar', extra_css_class='test')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<h3 data-field-id="None" class="test">Foobar</h3>' in str(form.render())

    # test for variable substitution
    pub.substitutions.feed(MockSubstitutionVariables())
    field = fields.TitleField(label='{{ bar }}')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<h3 data-field-id="None">Foobar</h3>' in str(form.render())

    pub.substitutions.feed(MockSubstitutionVariables())
    field = fields.TitleField(label='[bar]')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<h3 data-field-id="None">Foobar</h3>' in str(form.render())

    # test for proper escaping of substitution variables
    pub.substitutions.feed(MockSubstitutionVariables())
    field = fields.TitleField(label='{{ foo }}')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<h3 data-field-id="None">1 &lt; 3</h3>' in str(form.render())

    pub.substitutions.feed(MockSubstitutionVariables())
    field = fields.TitleField(label='[foo]')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<h3 data-field-id="None">1 &lt; 3</h3>' in str(form.render())

    # test for html content
    field = fields.TitleField(label='<i>Foobar&eacute;</i>')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '&lt;i&gt;Foobar&amp;eacute;&lt;/i&gt;' in str(form.render())
    assert field.unhtmled_label == 'Foobaré'

def test_subtitle():
    field = fields.SubtitleField(label='Foobar')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<h4 data-field-id="None">Foobar</h4>' in str(form.render())

    field = fields.SubtitleField(label='Foobar', extra_css_class='test')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<h4 data-field-id="None" class="test">Foobar</h4>' in str(form.render())

    # test for variable substitution
    pub.substitutions.feed(MockSubstitutionVariables())
    field = fields.SubtitleField(label='{{ bar }}')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<h4 data-field-id="None">Foobar</h4>' in str(form.render())

    pub.substitutions.feed(MockSubstitutionVariables())
    field = fields.SubtitleField(label='[bar]')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<h4 data-field-id="None">Foobar</h4>' in str(form.render())

    # test for proper escaping of substitution variables
    pub.substitutions.feed(MockSubstitutionVariables())
    field = fields.SubtitleField(label='{{ foo }}')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<h4 data-field-id="None">1 &lt; 3</h4>' in str(form.render())

    pub.substitutions.feed(MockSubstitutionVariables())
    field = fields.SubtitleField(label='[foo]')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<h4 data-field-id="None">1 &lt; 3</h4>' in str(form.render())

    # test for html content
    field = fields.SubtitleField(label='<i>Foobar&eacute;</i>')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '&lt;i&gt;Foobar&amp;eacute;&lt;/i&gt;' in str(form.render())
    assert field.unhtmled_label == 'Foobaré'

def test_comment():
    field = fields.CommentField(label='Foobar')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert BeautifulSoup(str(form.render())).find('div').text.strip() == 'Foobar'

    field = fields.CommentField(label='Foo\n\nBar\n\nBaz')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert re.match(  # regex to handle different beautifulsoup behaviours
            '<div class="comment-field\s?">\n<p>Foo</p>\n<p>Bar</p>\n<p>Baz</p>\n</div>',
            str(BeautifulSoup(str(form.render())).find('div')))

    # test for variable substitution
    pub.substitutions.feed(MockSubstitutionVariables())
    field = fields.CommentField(label='{{ bar }}')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert BeautifulSoup(str(form.render())).find('div').text.strip() == 'Foobar'

    field = fields.CommentField(label='[bar]')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert BeautifulSoup(str(form.render())).find('div').text.strip() == 'Foobar'

    # test for proper escaping of substitution variables
    field = fields.CommentField(label='{{ foo }}')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '1 &lt; 3' in str(form.render())

    field = fields.CommentField(label='[foo]')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '1 &lt; 3' in str(form.render())

    # test for html content
    field = fields.CommentField(label='<p>Foobar</p>')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert '<p>Foobar</p>' in str(form.render())
    assert field.unhtmled_label == 'Foobar'

    field = fields.CommentField(label='<p>Foobar&eacute;</p>')
    assert field.unhtmled_label == 'Foobaré'

def test_map():
    assert fields.MapField().get_json_value('42.2;10.2') == {'lat': 42.2, 'lon': 10.2}
    assert fields.MapField().get_json_value('-42.2;10.2') == {'lat': -42.2, 'lon': 10.2}
    assert fields.MapField().get_json_value(' 42.2 ; 10.2 ') == {'lat': 42.2, 'lon': 10.2}
    assert fields.MapField().get_json_value('') == None
    assert fields.MapField().get_json_value('foobar') == None

def test_item_render():
    items_kwargs = []
    items_kwargs.append({'items': ['aa', 'ab', 'ac']})
    items_kwargs.append({'data_source': {
        'type': 'formula',
        'value': '''['aa', 'ab', 'ac']'''}})

    for item_kwargs in items_kwargs:
        field = fields.ItemField(id='1', label='Foobar', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('<option') == 3

        field = fields.ItemField(id='1', label='Foobar', required=False,
                **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('<option') == 3

        field = fields.ItemField(id='1', label='Foobar',
                required=False, hint='Bla bla bla', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('<option value="" data-hint="Bla bla bla">Bla bla bla</option>') == 1 # ---
        assert str(form.render()).count('<option') == 4

        field = fields.ItemField(id='1', label='Foobar',
                required=True, hint='Bla bla bla', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('<option value="" data-hint="Bla bla bla">Bla bla bla</option>') == 1 # ---
        assert str(form.render()).count('<option') == 4

    items_kwargs = []
    items_kwargs.append({'items': None})
    items_kwargs.append({'items': []})
    items_kwargs.append({'data_source': {
        'type': 'formula',
        'value': '''[]'''}})
    for item_kwargs in items_kwargs:
        field = fields.ItemField(id='1', label='Foobar', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('<option') == 1

        field = fields.ItemField(id='1', label='Foobar', required=False,
                **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('<option') == 1

        field = fields.ItemField(id='1', label='Foobar',
                required=False, hint='Bla bla bla', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('<option value="" data-hint="Bla bla bla">Bla bla bla</option>') == 1 # ---
        assert str(form.render()).count('<option') == 1

        field = fields.ItemField(id='1', label='Foobar',
                required=True, hint='Bla bla bla', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('<option value="" data-hint="Bla bla bla">Bla bla bla</option>') == 1 # ---
        assert str(form.render()).count('<option') == 1

def test_item_render_as_autocomplete():
    field = fields.ItemField(
            id='1',
            label='Foobar',
            items=['aa', 'ab', 'ac'],
            display_mode='autocomplete')
    form = Form(use_tokens=False)
    field.add_to_form(form)
    assert str(form.render()).count('<option') == 3
    assert 'data-autocomplete' in str(form.render())

def test_item_render_as_radio():
    items_kwargs = []
    items_kwargs.append({'items': ['aa', 'ab', 'ac']})
    items_kwargs.append({'data_source': {
        'type': 'formula',
        'value': '''['aa', 'ab', 'ac']'''}})

    for item_kwargs in items_kwargs:
        field = fields.ItemField(id='1', label='Foobar', display_mode='radio', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('"radio"') == 3

        field = fields.ItemField(id='1', label='Foobar', required=False,
                display_mode='radio', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('"radio"') == 3

        field = fields.ItemField(id='1', label='Foobar',
                display_mode='radio', required=False, hint='Bla bla bla', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('"radio"') == 3

        field = fields.ItemField(id='1', label='Foobar',
                display_mode='radio', required=True, hint='Bla bla bla', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('"radio"') == 3

    items_kwargs = []
    items_kwargs.append({'items': None})
    items_kwargs.append({'items': []})
    items_kwargs.append({'data_source': {
        'type': 'formula',
        'value': '''[]'''}})
    for item_kwargs in items_kwargs:
        field = fields.ItemField(id='1', label='Foobar', display_mode='radio', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('"radio"') == 1

        field = fields.ItemField(id='1', label='Foobar', required=False,
                display_mode='radio', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('"radio"') == 1

        field = fields.ItemField(id='1', label='Foobar',
                display_mode='radio', required=False, hint='Bla bla bla', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('"radio"') == 1

        field = fields.ItemField(id='1', label='Foobar',
                display_mode='radio', required=True, hint='Bla bla bla', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('"radio"') == 1

def test_item_radio_lengths():
    field = fields.ItemField(id='1', label='Foobar', display_mode='radio',
            items=['aa', 'ab', 'ac'])
    form = Form(use_tokens=False)
    field.add_to_form(form)
    form.render()
    assert str(form.widgets[-1].render()).count('<br') == 0

    field = fields.ItemField(id='1', label='Foobar', display_mode='radio',
            items=['aa'*30, 'ab', 'ac'])
    form = Form(use_tokens=False)
    field.add_to_form(form)
    form.render()
    assert str(form.widgets[-1].render()).count('<br') == 2

    field = fields.ItemField(id='1', label='Foobar', display_mode='radio',
            items=['aa', 'ab'*30, 'ac'])
    form = Form(use_tokens=False)
    field.add_to_form(form)
    form.render()
    assert str(form.widgets[-1].render()).count('<br') == 2

    field = fields.ItemField(id='1', label='Foobar', display_mode='radio',
            items=['aa', 'ab', 'ac', 'ad'])
    form = Form(use_tokens=False)
    field.add_to_form(form)
    form.render()
    assert str(form.widgets[-1].render()).count('<br') == 3

def test_items_render():
    items_kwargs = []
    items_kwargs.append({'items': ['aa', 'ab', 'ac']})
    items_kwargs.append({'data_source': {
        'type': 'formula',
        'value': '''['aa', 'ab', 'ac']'''}})

    for item_kwargs in items_kwargs:
        field = fields.ItemsField(id='1', label='Foobar', **item_kwargs)
        form = Form(use_tokens=False)
        field.add_to_form(form)
        assert str(form.render()).count('type="checkbox"') == 3
        assert '>aa<' in str(form.render())
        assert '>ab<' in str(form.render())
        assert '>ac<' in str(form.render())

def test_ranked_items():
    field = fields.RankedItemsField(id='1', label='Foobar', items=['aa', 'ab', 'ac'])
    assert len(field.get_csv_heading()) == 3
    assert field.get_csv_value({'aa': 2, 'ab': 1, 'ac': 3}) == ['ab', 'aa', 'ac']

def test_table_rows():
    field = fields.TableRowsField(id='1', label='Foobar', columns=['aa', 'ab', 'ac'], total_row=False)
    html_table = str(field.get_view_value([['A', 'B', 'C'], ['D', 'E', 'F']]))
    assert html_table.count('<tr>') == 3
    assert html_table.count('<th>') == 3
    assert html_table.count('<td>') == 6
    assert html_table.count('<tfoot>') == 0
    for letter in 'ABCDEF':
        assert '>%s<' % letter in html_table

    rst_table = field.get_rst_view_value([['A', 'B', 'C'], ['D', 'E', 'F']])
    assert rst_table.count('==') == 9

    # check it doesn't crash when new columns are defined
    html_table = str(field.get_view_value([['A', 'B'], ['D', 'E']]))
    assert html_table.count('<tr>') == 3
    assert html_table.count('<th>') == 3
    assert html_table.count('<td>') == 6
    for letter in 'ABDE':
        assert '>%s<' % letter in html_table
    assert html_table.count('<td></td>') == 2

    rst_table = field.get_rst_view_value([['A', 'B'], ['D', 'E']])
    assert rst_table.count('==') == 9
    assert 'A  B  -' in rst_table
    assert 'D  E  -' in rst_table

    # check total rows
    field = fields.TableRowsField(id='1', label='Foobar', columns=['aa', 'ab', 'ac'], total_row=True)
    html_table = str(field.get_view_value([['A', 'B', '10'], ['D', 'E', '20']]))
    assert html_table.count('<tr>') == 4
    assert html_table.count('<th>') == 3
    assert html_table.count('<td>') == 9
    assert html_table.count('<tfoot>') == 1
    assert '<td>30.00</td>' in html_table
