#!/usr/bin/env python
#
# Copyright (c), 2016-2020, SISSA (International School for Advanced Studies).
# All rights reserved.
# This file is distributed under the terms of the MIT License.
# See the file 'LICENSE' in the root directory of the present
# distribution, or http://opensource.org/licenses/MIT.
#
# @author Davide Brunato <brunato@sissa.it>
#
"""Tests concerning XML documents"""

import unittest
import os
import io
import pathlib
import tempfile
from decimal import Decimal

try:
    import lxml.etree as lxml_etree
except ImportError:
    lxml_etree = None

from xmlschema import XMLSchema10, XMLSchema11, XmlDocument, \
    XMLResourceError, XMLSchemaValidationError, XMLSchemaDecodeError, \
    to_json, from_json

from xmlschema.etree import ElementTree
from xmlschema.names import XSD_NAMESPACE, XSI_NAMESPACE
from xmlschema.helpers import is_etree_element, is_etree_document
from xmlschema.resources import XMLResource
from xmlschema.documents import get_context


TEST_CASES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_cases/')


def casepath(relative_path):
    return os.path.join(TEST_CASES_DIR, relative_path)


class TestXmlDocuments(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.vh_dir = casepath('examples/vehicles')
        cls.vh_xsd_file = casepath('examples/vehicles/vehicles.xsd')
        cls.vh_xml_file = casepath('examples/vehicles/vehicles.xml')

        cls.col_dir = casepath('examples/collection')
        cls.col_xsd_file = casepath('examples/collection/collection.xsd')
        cls.col_xml_file = casepath('examples/collection/collection.xml')

    def test_to_json_api(self):
        json_data = to_json(self.col_xml_file, lazy=True)
        self.assertIsInstance(json_data, str)
        self.assertIn('"@xmlns:col"', json_data)
        self.assertIn(r'"name": "Joan Mir\u00f3"', json_data)

        with self.assertRaises(TypeError) as ctx:
            to_json(self.col_xml_file, lazy=True, decimal_type=Decimal)
        self.assertIn("is not JSON serializable", str(ctx.exception))

        col_1_error_xml_file = casepath('examples/collection/collection-1_error.xml')
        json_data, errors = to_json(col_1_error_xml_file, validation='lax', lazy=True)
        self.assertEqual(len(errors), 1)
        self.assertIsInstance(errors[0], XMLSchemaDecodeError)
        self.assertIn('"position": null', json_data)

        json_data, errors = to_json(col_1_error_xml_file, validation='lax', lazy=True,
                                    json_options={'default': lambda x: None})
        self.assertEqual(len(errors), 0)
        self.assertIn('"object": [null, null]', json_data)

    def test_from_json_api(self):
        json_data = to_json(self.col_xml_file, lazy=True)
        with self.assertRaises(TypeError) as ctx:
            from_json(json_data, self.col_xsd_file)
        self.assertIn("invalid type <class 'str'> for argument 'schema'", str(ctx.exception))

        col_schema = XMLSchema10(self.col_xsd_file)
        collection = from_json(json_data, schema=col_schema)
        self.assertEqual(collection.tag, '{http://example.com/ns/collection}collection')

        col_schema = XMLSchema10(self.col_xsd_file)
        collection = from_json(json_data, col_schema, json_options={'parse_float': Decimal})
        self.assertEqual(collection.tag, '{http://example.com/ns/collection}collection')

    def test_get_context_with_schema(self):
        source, schema = get_context(self.col_xml_file)
        self.assertIsInstance(source, XMLResource)
        self.assertIsInstance(schema, XMLSchema10)

        source, schema = get_context(self.col_xml_file, self.col_xsd_file)
        self.assertIsInstance(source, XMLResource)
        self.assertIsInstance(schema, XMLSchema10)

        col_schema = XMLSchema10(self.col_xsd_file)
        source, schema = get_context(self.col_xml_file, col_schema)
        self.assertIsInstance(source, XMLResource)
        self.assertIs(schema, col_schema)

        source, schema = get_context(self.vh_xml_file, cls=XMLSchema10)
        self.assertIsInstance(source, XMLResource)
        self.assertIsInstance(schema, XMLSchema10)

        source, schema = get_context(self.col_xml_file, cls=XMLSchema11)
        self.assertIsInstance(source, XMLResource)
        self.assertIsInstance(schema, XMLSchema11)

        source, schema = get_context(XMLResource(self.vh_xml_file))
        self.assertIsInstance(source, XMLResource)
        self.assertIsInstance(schema, XMLSchema10)

        xml_document = XmlDocument(self.vh_xml_file)
        source, schema = get_context(xml_document)
        self.assertIsInstance(source, XMLResource)
        self.assertIsInstance(schema, XMLSchema10)
        self.assertIs(xml_document.schema, schema)

        # Issue #145
        with open(self.vh_xml_file) as f:
            source, schema = get_context(f, schema=self.vh_xsd_file)
            self.assertIsInstance(source, XMLResource)
            self.assertIsInstance(schema, XMLSchema10)

        with open(self.vh_xml_file) as f:
            source, schema = get_context(XMLResource(f), schema=self.vh_xsd_file)
            self.assertIsInstance(source, XMLResource)
            self.assertIsInstance(schema, XMLSchema10)

        with open(self.vh_xml_file) as f:
            source, schema = get_context(f, base_url=self.vh_dir)
            self.assertIsInstance(source, XMLResource)
            self.assertIsInstance(schema, XMLSchema10)

    def test_get_context_without_schema(self):
        xml_data = '<text xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' \
                   '      xmlns:xs="http://www.w3.org/2001/XMLSchema"\n' \
                   '      xsi:type="xs:string">foo</text>'

        source, schema = get_context(xml_data)
        self.assertIsInstance(source, XMLResource)
        self.assertIs(schema, XMLSchema10.meta_schema)
        self.assertEqual(source.root.tag, 'text')
        self.assertTrue(schema.is_valid(source))

        with self.assertRaises(ValueError) as ctx:
            get_context('<empty/>')
        self.assertEqual(str(ctx.exception),
                         "no schema can be retrieved for the provided XML data")

        source, schema = get_context('<empty/>', dummy_schema=True)
        self.assertEqual(source.root.tag, 'empty')
        self.assertIsInstance(schema, XMLSchema10)

        col_xml_resource = XMLResource(self.col_xml_file)
        col_xml_resource.root.attrib.clear()
        self.assertEqual(col_xml_resource.get_locations(), [])

        source, schema = get_context(col_xml_resource, self.col_xsd_file)
        self.assertIs(source, col_xml_resource)
        self.assertIsInstance(schema, XMLSchema10)
        self.assertEqual(schema.target_namespace, 'http://example.com/ns/collection')

        # Schema target namespace doesn't match source namespace
        vh_schema = XMLSchema10(self.vh_xsd_file)

        source, schema = get_context(col_xml_resource, vh_schema)
        self.assertIs(source, col_xml_resource)
        self.assertIs(schema, vh_schema)
        self.assertFalse(schema.is_valid(source))

        vh_schema.import_schema('http://example.com/ns/collection', self.col_xsd_file)
        vh_schema.build()

        source, schema = get_context(col_xml_resource, vh_schema)
        self.assertIs(source, col_xml_resource)
        self.assertIs(schema, vh_schema)
        self.assertTrue(schema.is_valid(source))

    def test_xml_document_init_with_schema(self):
        xml_document = XmlDocument(self.vh_xml_file)
        self.assertEqual(os.path.basename(xml_document.url), 'vehicles.xml')
        self.assertEqual(xml_document.errors, ())
        self.assertIsInstance(xml_document.schema, XMLSchema10)

        xml_document = XmlDocument(self.vh_xml_file, cls=XMLSchema11)
        self.assertIsInstance(xml_document.schema, XMLSchema11)

        xml_document = XmlDocument(self.vh_xml_file, self.vh_xsd_file)
        self.assertIsInstance(xml_document.schema, XMLSchema10)

        vh_schema = XMLSchema10(self.vh_xsd_file)
        xml_document = XmlDocument(self.vh_xml_file, vh_schema)
        self.assertIsInstance(xml_document.schema, XMLSchema10)

        with self.assertRaises(XMLSchemaValidationError) as ctx: