# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import collections
import unittest

from lxml import etree as ET
from lxml.builder import E

import odoo
from odoo.modules.module import get_resource_path
from odoo.tests import common
from odoo.tools.convert import convert_file, xml_import, _eval_xml

Field = E.field
Value = E.value

class TestEvalXML(common.TransactionCase):
    def eval_xml(self, node, obj=None):
        return _eval_xml(obj, node, self.env)

    def test_char(self):
        self.assertEqual(
            self.eval_xml(Field("foo")),
            "foo")
        self.assertEqual(
            self.eval_xml(Field("None")),
            "None")

    def test_int(self):
        self.assertIsNone(
            self.eval_xml(Field("None", type='int')),
            "what the fuck?")
        self.assertEqual(
            self.eval_xml(Field(" 42  ", type="int")),
            42)

        with self.assertRaises(ValueError):
            self.eval_xml(Field("4.82", type="int"))

        with self.assertRaises(ValueError):
            self.eval_xml(Field("Whelp", type="int"))

    def test_float(self):
        self.assertEqual(
            self.eval_xml(Field("4.78", type="float")),
            4.78)

        with self.assertRaises(ValueError):
            self.eval_xml(Field("None", type="float"))

        with self.assertRaises(ValueError):
            self.eval_xml(Field("Foo", type="float"))

    def test_list(self):
        self.assertEqual(
            self.eval_xml(Field(type="list")),
            [])

        self.assertEqual(
            self.eval_xml(Field(
                Value("foo"),
                Value("5", type="int"),
                Value("4.76", type="float"),
                Value("None", type="int"),
                type="list"
            )),
            ["foo", 5, 4.76, None])

    def test_file(self):
        Obj = collections.namedtuple('Obj', ['module', 'idref'])
        obj = Obj('test_convert', None)
        self.assertEqual(
            self.eval_xml(Field('test_file.txt', type='file'), obj),
            'test_convert,test_file.txt')

        with self.assertRaises(IOError):
            self.eval_xml(Field('test_nofile.txt', type='file'), obj)

    def test_function(self):
        obj = xml_import(self.env, 'test_convert', None, 'init')

        # pass args in eval
        xml = E.function(
            model="test_convert.usered",
            name="model_method",
            eval="[1, 2]",
        )
        rec, args, kwargs = self.eval_xml(xml, obj)
        self.assertEqual(rec.env.context, self.env.context)
        self.assertEqual(rec.ids, [])
        self.assertEqual(args, (1, 2))
        self.assertEqual(kwargs, {})

        xml = E.function(
            model="test_convert.usered",
            name="method",
            eval="[1, 2]",
        )
        rec, args, kwargs = self.eval_xml(xml, obj)
        self.assertEqual(rec.env.context, self.env.context)
        self.assertEqual(rec.ids, [1])
        self.assertEqual(args, (2,))
        self.assertEqual(kwargs, {})

        # pass args in child elements
        xml = E.function(
            E.value(eval="1"), E.value(eval="2"),
            model="test_convert.usered",
            name="model_method",
        )
        rec, args, kwargs = self.eval_xml(xml, obj)
        self.assertEqual(rec.env.context, self.env.context)
        self.assertEqual(rec.ids, [])
        self.assertEqual(args, (1, 2))
        self.assertEqual(kwargs, {})

        xml = E.function(
            E.value(eval="1"), E.value(eval="2"),
            model="test_convert.usered",
            name="method",
        )
        rec, args, kwargs = self.eval_xml(xml, obj)
        self.assertEqual(rec.env.context, self.env.context)
        self.assertEqual(rec.ids, [1])
        self.assertEqual(args, (2,))
        self.assertEqual(kwargs, {})

    def test_function_kwargs(self):
        obj = xml_import(self.env, 'test_convert', None, 'init')

        # pass args and kwargs in child elements
        xml = E.function(
            E.value(eval="1"), E.value(name="foo", eval="2"),
            model="test_convert.usered",
            name="model_method",
        )
        rec, args, kwargs = self.eval_xml(xml, obj)
        self.assertEqual(rec.env.context, self.env.context)
        self.assertEqual(rec.ids, [])
        self.assertEqual(args, (1,))
        self.assertEqual(kwargs, {'foo': 2})

        xml = E.function(
            E.value(eval="1"), E.value(name="foo", eval="2"),
            model="test_convert.usered",
            name="method",
        )
        rec, args, kwargs = self.eval_xml(xml, obj)
        self.assertEqual(rec.env.context, self.env.context)
        self.assertEqual(rec.ids, [1])
        self.assertEqual(args, ())
        self.assertEqual(kwargs, {'foo': 2})

        # pass args and context in kwargs
        xml = E.function(
            E.value(eval="1"), E.value(name="context", eval="{'foo': 2}"),
            model="test_convert.usered",
            name="model_method",
        )
        rec, args, kwargs = self.eval_xml(xml, obj)
        self.assertEqual(rec.env.context, {'foo': 2})
        self.assertEqual(rec.ids, [])
        self.assertEqual(args, (1,))
        self.assertEqual(kwargs, {})

        xml = E.function(
            E.value(eval="1"), E.value(name="context", eval="{'foo': 2}"),
            model="test_convert.usered",
            name="method",
        )
        rec, args, kwargs = self.eval_xml(xml, obj)
        self.assertEqual(rec.env.context, {'foo': 2})
        self.assertEqual(rec.ids, [1])
        self.assertEqual(args, ())
        self.assertEqual(kwargs, {})

    def test_function_function(self):
        obj = xml_import(self.env, 'test_convert', None, 'init')

        xml = E.function(
            E.function(model="test_convert.usered", name="search", eval="[[]]"),
            model="test_convert.usered",
            name="method",
        )
        rec, args, kwargs = self.eval_xml(xml, obj)
        self.assertEqual(rec.env.context, {})
        self.assertEqual(rec.ids, [])
        self.assertEqual(args, ())
        self.assertEqual(kwargs, {})

    def test_o2m_sub_records(self):
        # patch the model's class with a proxy that copies the argument
        Model = self.registry['test_convert.test_model']
        call_args = []

        def _load_records(self, data_list, update=False):
            call_args.append(data_list)
            # pylint: disable=bad-super-call
            return super(Model, self)._load_records(data_list, update=update)

        self.patch(Model, '_load_records', _load_records)

        # import a record with a subrecord
        xml = ET.fromstring("""
            <record id="test_convert.test_o2m_record" model="test_convert.test_model">
                <field name="usered_ids">
                    <record id="test_convert.test_o2m_subrecord" model="test_convert.usered">
                        <field name="name">subrecord</field>
                    </record>
                </field>
            </record>
        """.strip())
        obj = xml_import(self.env, 'test_convert', None, 'init')
        obj._tag_record(xml)

        # check that field 'usered_ids' is not passed
        self.assertEqual(len(call_args), 1)
        for data in call_args[0]:
            self.assertNotIn('usered_ids', data['values'],
                             "Unexpected value in O2M When loading XML with sub records")

    def test_translated_field(self):
        """Tests importing a data file with a lang set in the environment sets the source (en_US)
        rather than setting the translation (fr_FR)
        This is currently the expected behavior.
        Maybe one day we would like that `convert_file` and `xml_import` respect the environment lang,
        but currently the API, and code blocks using `convert_file`/`xml_import` expect to set the source (en_US)
        """
        self.env['res.lang']._activate_lang('fr_FR')
        env_fr = self.env(context=dict(self.env.context, lang='fr_FR'))
        record = self.env.ref('test_convert.test_translated_field')

        # 1. Test xml_import, which is sometimes imported and used directly in addons' code
        # Change the value of the record `name` field
        record.name = 'bar'
        self.assertEqual(record.name, 'bar')
        # Reset the value to the one from the XML data file,
        # with a lang passed in the environment.
        filepath = get_resource_path('test_convert', 'data/test_translated_field/test_model_data.xml')
        doc = ET.parse(filepath)
        obj = xml_import(env_fr, 'test_convert', {}, mode='init', xml_filename=filepath)
        obj.parse(doc.getroot())
        self.assertEqual(record.with_context(lang=None).name, 'foo')

        # 2. Test convert_file with an XML
        # Change the value of the record `name` field
        record.name = 'bar'
        self.assertEqual(record.name, 'bar')
        # Reset the value to the one from the XML data file,
        # with a lang passed in the environment.
        convert_file(env_fr, 'test_convert', 'data/test_translated_field/test_model_data.xml', {})
        self.assertEqual(record.with_context(lang=None).name, 'foo')

        # 3. Test convert_file with a CSV
        # Change the value of the record `name` field
        record.name = 'bar'
        self.assertEqual(record.name, 'bar')
        # Reset the value to the one from the XML data file,
        # with a lang passed in the environment.
        convert_file(env_fr, 'test_convert', 'data/test_translated_field/test_convert.test_model.csv', {})
        self.assertEqual(record.with_context(lang=None).name, 'foo')

    @unittest.skip("not tested")
    def test_xml(self):
        pass

    def test_html(self):
        self.assertEqual(
            self.eval_xml(Field(ET.fromstring(
            """<parent>
                <t t-if="True">
                    <t t-out="'text'"/>
                </t>
                <t t-else="">
                    <t t-out="'text2'"></t>
                </t>
            </parent>"""), type="html")),
            """<parent>
                <t t-if="True">
                    <t t-out="'text'"></t>
                </t>
                <t t-else="">
                    <t t-out="'text2'"></t>
                </t>
            </parent>""",
            "Evaluating an HTML field should give empty nodes instead of self-closing tags"
        )
