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

import pytest
import sys
import shutil
import time
import xml.etree.ElementTree as ET

from django.utils.six import BytesIO, StringIO
from quixote import cleanup

from wcs.categories import Category
from wcs.formdef import FormDef, fields, FormdefImportError
from wcs.workflows import Workflow
from wcs.roles import Role
from wcs.qommon.misc import indent_xml as indent

from utilities import create_temporary_pub


def setup_module(module):
    cleanup()
    global pub
    pub = create_temporary_pub()


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

def export_to_indented_xml(formdef, include_id=False):
    formdef_xml = ET.fromstring(ET.tostring(formdef.export_to_xml(include_id=include_id)))
    indent(formdef_xml)
    return formdef_xml

def assert_compare_formdef(formdef1, formdef2, include_id=False):
    assert ET.tostring(export_to_indented_xml(formdef1, include_id=include_id)) == \
            ET.tostring(export_to_indented_xml(formdef2, include_id=include_id))
    assert formdef1.export_to_json(include_id=include_id, indent=2) == \
            formdef2.export_to_json(include_id=include_id, indent=2)

def assert_xml_import_export_works(formdef, include_id=False):
    formdef_xml = formdef.export_to_xml(include_id=include_id)
    formdef2 = FormDef.import_from_xml_tree(formdef_xml, include_id=include_id)
    assert_compare_formdef(formdef, formdef2, include_id=include_id)
    return formdef2

def assert_json_import_export_works(formdef, include_id=False):
    formdef2 = FormDef.import_from_json(
            StringIO(formdef.export_to_json(include_id=include_id)), include_id=include_id)
    assert_compare_formdef(formdef, formdef2, include_id=include_id)
    return formdef2

def test_empty():
    formdef = FormDef()
    formdef.name = 'empty'
    assert_xml_import_export_works(formdef)
    assert_json_import_export_works(formdef)

def test_text_attributes():
    formdef = FormDef()
    formdef.name = 'Foo'
    formdef.url_name = 'foo'
    f2 = assert_xml_import_export_works(formdef)
    assert f2.url_name == formdef.url_name
    f2 = assert_json_import_export_works(formdef)
    assert f2.url_name == formdef.url_name

def test_boolean_attributes():
    formdef = FormDef()
    formdef.name = 'Foo'
    formdef.url_name = 'foo'
    formdef.confirmation = True
    formdef.enable_tracking_codes = True
    f2 = assert_xml_import_export_works(formdef)
    assert f2.enable_tracking_codes == formdef.enable_tracking_codes
    assert f2.confirmation == formdef.confirmation
    f2 = assert_json_import_export_works(formdef)
    assert f2.enable_tracking_codes == formdef.enable_tracking_codes
    assert f2.confirmation == formdef.confirmation

def test_a_field():
    formdef = FormDef()
    formdef.name = 'Foo'
    formdef.fields = [
            fields.StringField(type='string', id=1, label='Bar', size='40')
            ]
    f2 = assert_xml_import_export_works(formdef)
    assert len(f2.fields) == len(formdef.fields)
    f2 = assert_json_import_export_works(formdef)
    assert len(f2.fields) == len(formdef.fields)

def test_more_fields():
    formdef = FormDef()
    formdef.name = 'Blah'
    formdef.fields = [
            fields.TextField(type='text', label='Bar', pre=True),
            fields.EmailField(type='email', label='Bar'),
            fields.BoolField(type='bool', label='Bar'),
            fields.DateField(type='date', label='Bar', minimum_date='2014-01-01'),
            fields.ItemField(type='item', label='Bar', items=['foo', 'bar', 'baz']),
            ]
    f2 = assert_xml_import_export_works(formdef)
    assert len(f2.fields) == len(formdef.fields)
    assert f2.fields[2].type == formdef.fields[2].type
    assert f2.fields[3].minimum_date == formdef.fields[3].minimum_date
    assert f2.fields[4].items == formdef.fields[4].items

    f2 = assert_json_import_export_works(formdef)
    assert len(f2.fields) == len(formdef.fields)
    assert f2.fields[2].type == formdef.fields[2].type
    assert f2.fields[3].minimum_date == formdef.fields[3].minimum_date
    assert f2.fields[4].items == formdef.fields[4].items

def test_item_radio():
    formdef = FormDef()
    formdef.name = 'Blah'
    formdef.fields = [
        fields.ItemField(
            type='item',
            label='Bar',
            items=['foo', 'bar', 'baz'],
            list_mode='radio',
            id='1',
        ),
    ]

    # test new mode
    assert_json_import_export_works(formdef, include_id=True)

    # test conversion of legacy show_as_radio attribute
    formdef_xml = formdef.export_to_xml(include_id=True)
    field = formdef_xml.findall('fields/field')[0]
    ET.SubElement(field, 'show_as_radio').text = 'True'
    field.remove(field.find('display_mode'))
    fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=True)
    assert fd2.fields[0].display_mode == 'radio'

    # test conversion of legacy show_as_radio attribute
    formdef_xml = formdef.export_to_xml(include_id=True)
    field = formdef_xml.findall('fields/field')[0]
    ET.SubElement(field, 'show_as_radio').text = 'False'
    field.remove(field.find('display_mode'))
    fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=True)
    assert fd2.fields[0].display_mode == 'list'

def test_include_id():
    formdef = FormDef()
    formdef.name = 'Blah'
    formdef.fields = [
            fields.TextField(type='text', label='Bar', pre=True),
            fields.EmailField(type='email',  label='Bar'),
            fields.BoolField(type='bool', label='Bar'),
            fields.DateField(type='date', label='Bar', minimum_date='2014-01-01'),
            fields.ItemField(type='item', label='Bar', items=['foo', 'bar', 'baz']),
            ]
    for field in formdef.fields:
        field.id = formdef.get_new_field_id()
    formdef.fields[4].id = '10'
    f2 = assert_xml_import_export_works(formdef, include_id=True)
    assert len(f2.fields) == len(formdef.fields)
    assert f2.fields[0].id == formdef.fields[0].id
    assert f2.fields[4].id == formdef.fields[4].id

    f2 = assert_json_import_export_works(formdef, include_id=True)
    assert len(f2.fields) == len(formdef.fields)
    assert f2.fields[0].id == formdef.fields[0].id
    assert f2.fields[4].id == formdef.fields[4].id

def test_modification_time():
    formdef = FormDef()
    formdef.name = 'empty'
    formdef.last_modification_time = time.localtime()
    assert_xml_import_export_works(formdef)
    f2 = assert_json_import_export_works(formdef)
    assert tuple(f2.last_modification_time)[:6] == tuple(formdef.last_modification_time)[:6]

def test_workflow_options():
    formdef = FormDef()
    formdef.name = 'workflow options'
    formdef.workflow_options = {'foo': 'bar',
            'foo2': 'baré'}
    fd2 = assert_xml_import_export_works(formdef)
    assert fd2.workflow_options == formdef.workflow_options
    fd2 = assert_json_import_export_works(formdef)
    assert fd2.workflow_options == formdef.workflow_options

def test_workflow_options_with_no_values():
    formdef = FormDef()
    formdef.name = 'foo'
    formdef.workflow_options = {'foo': None,
            'foo2': None}
    fd2 = assert_xml_import_export_works(formdef)
    assert fd2.workflow_options == formdef.workflow_options
    fd2 = assert_json_import_export_works(formdef)
    assert fd2.workflow_options == formdef.workflow_options

def test_workflow_options_with_file():
    from quixote.http_request import Upload
    from wcs.qommon.form import UploadedFile

    upload = Upload('/foo/bar', content_type='application/vnd.oasis.opendocument.text')
    file_content = b'''PK\x03\x04\x14\x00\x00\x08\x00\x00\'l\x8eG^\xc62\x0c\'\x00'''
    upload.fp = BytesIO()
    upload.fp.write(file_content)
    upload.fp.seek(0)
    model_file = UploadedFile(pub.APP_DIR, None, upload)

    formdef = FormDef()
    formdef.name = 'foo'
    formdef.workflow_options = {'foo': model_file}
    fd2 = assert_xml_import_export_works(formdef)
    assert formdef.workflow_options['foo'].filename == fd2.workflow_options['foo'].filename
    assert formdef.workflow_options['foo'].get_content() == fd2.workflow_options['foo'].get_content()
    fd2 = assert_json_import_export_works(formdef)
    assert formdef.workflow_options['foo'].filename == fd2.workflow_options['foo'].filename
    assert formdef.workflow_options['foo'].get_content() == fd2.workflow_options['foo'].get_content()

def test_workflow_options_with_date():
    formdef = FormDef()
    formdef.name = 'foo'
    formdef.workflow_options = {'foo': time.strptime('2014-02-02', '%Y-%m-%d')}
    fd2 = assert_xml_import_export_works(formdef)
    assert formdef.workflow_options['foo'] == fd2.workflow_options['foo']


def test_workflow_reference():
    Workflow.wipe()
    FormDef.wipe()

    wf = Workflow()
    wf.name = 'test workflow'
    wf.store()

    formdef = FormDef()
    formdef.name = 'foo'
    formdef.workflow_id = wf.id
    f2 = assert_xml_import_export_works(formdef)
    assert f2.workflow_id == formdef.workflow_id

    f2 = assert_xml_import_export_works(formdef, include_id=True)
    assert f2.workflow_id == formdef.workflow_id

    formdef_xml_with_id = formdef.export_to_xml(include_id=True)
    formdef_xml_without_id = formdef.export_to_xml(include_id=False)

    # check there's no reference to a non-existing workflow
    Workflow.wipe()
    assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=False).workflow_id is None
    assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=True).workflow_id is None

    # check an import that is not using id fields will find the workflow by its
    # name
    wf = Workflow()
    wf.id = '2'
    wf.name = 'test workflow'
    wf.store()
    assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=False).workflow_id == '2'
    assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=True).workflow_id is None

def test_category_reference():
    Category.wipe()
    FormDef.wipe()

    cat = Category()
    cat.name = 'test category'
    cat.store()

    formdef = FormDef()
    formdef.name = 'foo'
    formdef.category_id = cat.id
    f2 = assert_xml_import_export_works(formdef)
    assert f2.category_id == formdef.category_id

    f2 = assert_xml_import_export_works(formdef, include_id=True)
    assert f2.category_id == formdef.category_id

    formdef_xml_with_id = formdef.export_to_xml(include_id=True)
    formdef_xml_without_id = formdef.export_to_xml(include_id=False)

    # check there's no reference to a non-existing category
    Category.wipe()
    assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=False).category_id is None
    assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=True).category_id is None

    # check an import that is not using id fields will find the category by its
    # name
    cat = Category()
    cat.id = '2'
    cat.name = 'test category'
    cat.store()
    assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=False).category_id == '2'
    assert FormDef.import_from_xml_tree(formdef_xml_with_id, include_id=True).category_id is None


def test_file_field():
    formdef = FormDef()
    formdef.name = 'foo'
    formdef.fields = [fields.FileField(type='file', id='1', document_type={
        'id': 'justificatif-de-domicile',
        'fargo': True,
        'mimetypes': ['application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'image/*'],
    })]
    assert_xml_import_export_works(formdef, include_id=True)
    assert_xml_import_export_works(formdef)
    assert_json_import_export_works(formdef, include_id=True)
    assert_json_import_export_works(formdef)

def test_invalid_field_type():
    formdef = FormDef()
    formdef.name = 'foo'
    formdef.fields = [fields.StringField(id='1', type='XXX')]
    export = ET.tostring(export_to_indented_xml(formdef))
    with pytest.raises(FormdefImportError):
        FormDef.import_from_xml(BytesIO(export), include_id=True)

def test_unknown_data_source():
    formdef = FormDef()
    formdef.name = 'foo'
    formdef.fields = [fields.StringField(id='1', type='string',
        data_source={'type': 'json', 'value': 'http://example.net'})]
    export = ET.tostring(export_to_indented_xml(formdef))

    FormDef.import_from_xml(BytesIO(export))

    formdef.fields = [fields.StringField(id='1', type='string',
        data_source={'type': 'foobar'})]
    export = ET.tostring(export_to_indented_xml(formdef))
    with pytest.raises(FormdefImportError):
        FormDef.import_from_xml(BytesIO(export))

def test_duplicated_field_ids():
    formdef = FormDef()
    formdef.name = 'foo'
    formdef.fields = [
            fields.StringField(id='2', type='string'),
            fields.StringField(id='2', type='string'),
            fields.StringField(id='1', type='string')]
    export = ET.tostring(export_to_indented_xml(formdef, include_id=True))

    with pytest.raises(FormdefImportError):
        FormDef.import_from_xml(BytesIO(export))

    with pytest.raises(FormdefImportError):
        FormDef.import_from_xml(BytesIO(export), include_id=True)

    formdef2 = FormDef.import_from_xml(BytesIO(export), fix_on_error=True)
    assert formdef2.fields[0].id == '1'
    assert formdef2.fields[1].id == '2'
    assert formdef2.fields[2].id == '3'

def test_wrong_max_field_id():
    formdef = FormDef()
    formdef.name = 'foo'
    formdef.fields = [
            fields.StringField(id='1', type='string'),
            fields.StringField(id='2', type='string'),]
    formdef.max_field_id = 1
    export = ET.tostring(export_to_indented_xml(formdef, include_id=True))

    formdef2 = FormDef.import_from_xml(BytesIO(export), include_id=True)
    assert formdef2.max_field_id == 2

def test_page_condition():
    formdef = FormDef()
    formdef.name = 'foo'
    formdef.fields = [
        fields.PageField(id='1', type='page',
            condition={'type': 'python', 'value': 'blah'})
    ]
    fd2 = assert_xml_import_export_works(formdef)
    assert fd2.fields[0].type == 'page'
    assert fd2.fields[0].condition == formdef.fields[0].condition

    # test legacy condition (simple string)
    formdef_xml = formdef.export_to_xml(include_id=True)
    condition = formdef_xml.findall('fields/field/condition')[0]
    condition.clear()
    condition.text = 'blah'
    fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=True)
    assert fd2.fields[0].condition == formdef.fields[0].condition

def test_page_post_conditions():
    formdef = FormDef()
    formdef.name = 'foo'
    formdef.fields = [
        fields.PageField(id='1', type='page',
            post_conditions=[{'condition': {'type': 'python', 'value': 'blah'}, 'error_message': 'bar'}]),
    ]
    fd2 = assert_xml_import_export_works(formdef)
    assert fd2.fields[0].type == 'page'
    assert fd2.fields[0].post_conditions == formdef.fields[0].post_conditions

    # test legacy condition (simple string)
    formdef_xml = formdef.export_to_xml(include_id=True)
    post_condition = formdef_xml.findall('fields/field/post_conditions/post_condition/condition')[0]
    post_condition.clear()
    post_condition.text = 'blah'
    fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=True)
    assert fd2.fields[0].post_conditions[0]['condition'] == {'type': 'python', 'value': 'blah'}
    assert fd2.fields[0].post_conditions[0]['error_message'] == 'bar'

    # test incomplete post condition (not allowed anymore but old formdefs may
    # have this)
    formdef.fields = [
        fields.PageField(id='1', type='page',
            post_conditions=[{'condition': {'type': 'python', 'value': 'blah'}, 'error_message': None}]),
    ]
    formdef_xml = formdef.export_to_xml(include_id=True)
    fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=True)
    assert fd2.fields[0].post_conditions[0]['condition'] == {'type': 'python', 'value': 'blah'}
    assert fd2.fields[0].post_conditions[0]['error_message'] == ''

def test_workflow_roles():
    Role.wipe()
    role = Role(name='blah')
    role.store()

    formdef = FormDef()
    formdef.name = 'foo'
    formdef.fields = []
    formdef.workflow_roles = {'_receiver': role.id}
    fd2 = assert_xml_import_export_works(formdef, include_id=True)
    assert fd2.workflow_roles.get('_receiver') == role.id

    fd2 = assert_xml_import_export_works(formdef, include_id=False)
    assert fd2.workflow_roles.get('_receiver') == role.id

    xml_export = export_to_indented_xml(formdef, include_id=True)
    xml_export_no_id = export_to_indented_xml(formdef, include_id=False)

    # same id, different name
    role.name = 'blah 2'
    role.store()

    fd2 = FormDef.import_from_xml_tree(xml_export, include_id=True)
    assert fd2.workflow_roles.get('_receiver') == role.id
    fd2 = FormDef.import_from_xml_tree(xml_export, include_id=False)
    assert fd2.workflow_roles.get('_receiver') is None

    role.remove_self()
    fd2 = FormDef.import_from_xml_tree(xml_export, include_id=True)
    assert fd2.workflow_roles.get('_receiver') is None

def test_geolocations():
    formdef = FormDef()
    formdef.name = 'foo'
    formdef.fields = []
    formdef.geolocations = {'base': 'Base'}
    fd2 = assert_xml_import_export_works(formdef, include_id=True)
    assert fd2.geolocations == formdef.geolocations
    fd3 = assert_json_import_export_works(formdef)
    assert fd3.geolocations == formdef.geolocations

def test_user_roles():
    Role.wipe()

    role = Role(name='blah')
    role.store()

    formdef = FormDef()
    formdef.name = 'foo'
    formdef.fields = []
    formdef.roles = ['logged-users', role.id]
    fd2 = assert_xml_import_export_works(formdef, include_id=True)
    assert fd2.roles == formdef.roles

    formdef_xml = formdef.export_to_xml(include_id=True)
    formdef_xml_no_id = formdef.export_to_xml(include_id=False)
    role.remove_self()
    fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=True)
    assert fd2.roles == ['logged-users']
    fd2 = FormDef.import_from_xml_tree(formdef_xml, include_id=False)
    assert fd2.roles == ['logged-users']

    fd2 = FormDef.import_from_xml_tree(formdef_xml_no_id, include_id=True)
    assert fd2.roles == ['logged-users']
    fd2 = FormDef.import_from_xml_tree(formdef_xml_no_id, include_id=False)
    assert fd2.roles == ['logged-users']

def test_backoffice_submission_roles():
    Role.wipe()

    role = Role(name='blah')
    role.store()

    formdef = FormDef()
    formdef.name = 'foo'
    formdef.fields = []
    formdef.backoffice_submission_roles = [role.id]
    fd2 = assert_xml_import_export_works(formdef, include_id=True)
    assert fd2.backoffice_submission_roles == formdef.backoffice_submission_roles

def test_required_authentication_contexts():
    formdef = FormDef()
    formdef.name = 'foo'
    formdef.fields = []
    formdef.required_authentication_contexts = ['fedict']
    fd2 = assert_xml_import_export_works(formdef, include_id=True)
    assert fd2.required_authentication_contexts == formdef.required_authentication_contexts

def test_field_condition():
    formdef = FormDef()
    formdef.name = 'Foo'
    formdef.fields = [
            fields.StringField(type='string', id=1, label='Bar', size='40',
                condition={'type': 'django', 'value': '1'})
            ]
    f2 = assert_xml_import_export_works(formdef)
    assert len(f2.fields) == len(formdef.fields)
    assert f2.fields[0].condition == {'type': 'django', 'value': '1'}

def test_field_validation():
    formdef = FormDef()
    formdef.name = 'Foo'
    formdef.fields = [
        fields.StringField(type='string', id=1, label='Bar', size='40', validation={})
        ]
    f2 = assert_xml_import_export_works(formdef)
    assert len(f2.fields) == len(formdef.fields)
    assert not f2.fields[0].validation

    formdef.fields = [
        fields.StringField(type='string', id=1, label='Bar', size='40', validation=None)
        ]
    formdef_xml = formdef.export_to_xml()
    f2 = FormDef.import_from_xml_tree(formdef_xml)
    assert len(f2.fields) == len(formdef.fields)
    assert not f2.fields[0].validation

    formdef.fields = [
        fields.StringField(type='string', id=1, label='Bar', size='40',
            validation={'type': 'regex', 'value': r'\d'})
        ]
    f2 = assert_xml_import_export_works(formdef)
    assert len(f2.fields) == len(formdef.fields)
    assert f2.fields[0].validation == formdef.fields[0].validation

    # backward compatibility
    formdef_xml = formdef.export_to_xml()
    old_format = ET.tostring(formdef_xml).replace(
            b'<validation><type>regex</type><value>\\d</value></validation>',
            b'<validation>\\d</validation>')
    f2 = FormDef.import_from_xml(BytesIO(old_format))
    assert len(f2.fields) == len(formdef.fields)
    assert f2.fields[0].validation == {'type': 'regex', 'value': '\\d'}

def test_digest_template():
    formdef = FormDef()
    formdef.name = 'Foo'
    formdef.fields = []
    formdef.digest_template = '{{form_number}}'
    f2 = assert_xml_import_export_works(formdef)
    assert f2.digest_template == formdef.digest_template
