import glob
import pytest

from __init__ import parse, open, _ValidationError, CollectedValidationErrors, DuplicateNameError, HeaderFieldError
from contextlib import nullcontext


def create_context(fn):
    if "fail_" in fn:
        return pytest.raises(_ValidationError)
    else:
        return nullcontext()


@pytest.mark.parametrize("file", glob.glob("fixtures/*.ifc"))
def test_file_with_tree(file):
    with create_context(file):
        parse(filename=file, with_tree=True)


@pytest.mark.parametrize("file", glob.glob("fixtures/*.ifc"))
def test_file_without_tree(file):
    if any(sub in file for sub in ["fail_too_many_header_entity_fields.ifc", "fail_multiple_wrong_header_fields"]):
        pytest.skip("This file relies on header field validation using the parsed AST, "
                "but with_tree=False uses a NullTransformer that discards the AST, "
                "so validating the header field names is not possible in this mode.")
    with create_context(file):
        parse(filename=file, with_tree=False)


def test_parse_features():
    f = open('fixtures/pass_1.ifc')
    assert f.by_id(1).id == 1
    assert f.by_id(1).type == 'IFCPERSON'
    assert f.data_[1][0].type == 'IFCPERSON'
    assert f.by_type('ifcperson')[0].id == 1
    assert f[1][0] is None
    assert f.header.file_description[0][0] == 'ViewDefinition [CoordinationView]'
    assert f.header_.get('FILE_DESCRIPTION')[0][0]
    assert f.by_type('ifcapplication')[1][2] == "Nested ' quotes"


def test_parse_valid_header():
    f = open('fixtures/passing_header.ifc')

    expected_description = {
        "description": ('ViewDefinition [Alignment-basedView]',),
        "implementation_level": '2;1',
    }

    expected_name = {
        "name": 'Header example2.ifc',
        "time_stamp": '2022-09-16T10:35:07',
        "author": ('Evandro Alfieri',),
        "organization": ('buildingSMART Int.',),
        "preprocessor_version": 'IFC Motor 1.0',
        "originating_system": 'Company - Application - 26.0.0.0',
        "authorization": 'none',
    }

    expected_schema = {
        "schema_identifiers": ('IFC4X3_ADD2',),
    }

    for key, val in expected_description.items():
        assert getattr(f.header.file_description, key) == val, f"{key} mismatch"

    for key, val in expected_name.items():
        assert getattr(f.header.file_name, key) == val, f"{key} mismatch"

    for key, val in expected_schema.items():
        assert getattr(f.header.file_schema, key) == val, f"{key} mismatch"
        

def test_header_only_api():
    f = open('fixtures/passing_header.ifc', only_header=True)
    expected_description = {
        "description": ('ViewDefinition [Alignment-basedView]',),
        "implementation_level": '2;1',
    }

    expected_name = {
        "name": 'Header example2.ifc',
        "time_stamp": '2022-09-16T10:35:07',
        "author": ('Evandro Alfieri',),
        "organization": ('buildingSMART Int.',),
        "preprocessor_version": 'IFC Motor 1.0',
        "originating_system": 'Company - Application - 26.0.0.0',
        "authorization": 'none',
    }

    expected_schema = {
        "schema_identifiers": ('IFC4X3_ADD2',),
    }

    for key, val in expected_description.items():
        assert getattr(f.header.file_description, key) == val, f"{key} mismatch"

    for key, val in expected_name.items():
        assert getattr(f.header.file_name, key) == val, f"{key} mismatch"

    for key, val in expected_schema.items():
        assert getattr(f.header.file_schema, key) == val, f"{key} mismatch"

def test_file_mvd_attr():
    f = open('fixtures/extended_mvd.ifc', only_header=True)
    
    assert 'ReferenceView_V1.2' in f.mvd.view_definitions
    assert all(i in f.mvd.keywords for i in ['exchange_requirements', 'view_definitions', 'remark', 'comments'])
    assert 'Ramp' in f.mvd.options['ExcludedObjects']
    assert f.mvd.Remark['SomeKey'] == 'SomeValue'
    assert len(f.mvd.comments) == 2
    assert all(v in vars(f.header).keys() for v in ['file_description', 'file_name', 'file_schema'])
    assert len(f.header.file_name) == 7


@pytest.mark.parametrize("filename", [
    'fixtures/fail_invalid_header_entity.ifc',
    'fixtures/fail_no_header.ifc',
])
def test_invalid_headers_(filename):
    # error in header
    with pytest.raises(_ValidationError):
        parse(filename=filename, with_tree=False, only_header=True)

@pytest.mark.parametrize("filename", [
    'fixtures/fail_duplicate_id.ifc',
    'fixtures/fail_double_comma.ifc',
    'fixtures/fail_double_semi.ifc'
])
def test_valid_headers(filename):
    # error in body
    with nullcontext():
        parse(filename=filename, with_tree=False, only_header=True)

def test_header_entity_fields():
    with pytest.raises(_ValidationError):
        parse(filename='fixtures/fail_too_many_header_entity_fields.ifc', only_header=True)

def test_header_entity_fields_whole_file():
    with pytest.raises(_ValidationError):
        parse(filename='fixtures/fail_too_many_header_entity_fields.ifc')

def test_header_entity_fields_whole_file():
    with pytest.raises(CollectedValidationErrors) as exc_info:
        parse(filename="fixtures/fail_multiple_duplicate_ids.ifc")

    errors = exc_info.value.errors
    
    assert len(errors) == 2
    assert all(isinstance(e, DuplicateNameError) for e in errors)

def test_multiple_wrong_header_fields():
    with pytest.raises(CollectedValidationErrors) as exc_info:
        parse(filename="fixtures/fail_multiple_wrong_header_fields.ifc")

    errors = exc_info.value.errors
    
    assert len(errors) == 2
    assert all(isinstance(e, HeaderFieldError) for e in errors)