import sunspec2.mdef as mdef
import json
import copy
import pytest


def test_to_int():
    assert mdef.to_int('4') == 4
    assert isinstance(mdef.to_int('4'), int)
    assert isinstance(mdef.to_int(4.0), int)


def test_to_str():
    assert mdef.to_str(4) == '4'
    assert isinstance(mdef.to_str('4'), str)


def test_to_float():
    assert mdef.to_float('4') == 4.0
    assert isinstance(mdef.to_float('4'), float)
    assert mdef.to_float('z') is None


def test_to_number_type():
    assert mdef.to_number_type('4') == 4
    assert mdef.to_number_type('4.0') == 4.0
    assert mdef.to_number_type('z') == 'z'


def test_validate_find_point():
    with open('./sunspec2/models/json/model_702.json') as f:
        model_json = json.load(f)

    assert mdef.validate_find_point(model_json['group'], 'ID') == model_json['group']['points'][0]
    assert mdef.validate_find_point(model_json['group'], 'abc') is None


def test_validate_attrs():
    with open('./sunspec2/models/json/model_701.json') as f:
        model_json = json.load(f)

    # model
    assert mdef.validate_attrs(model_json, mdef.model_attr) == ''

    model_unexp_attr_err = copy.deepcopy(model_json)
    model_unexp_attr_err['abc'] = 'def'
    assert mdef.validate_attrs(model_unexp_attr_err, mdef.model_attr)[0:37] == 'Unexpected model definition attribute'

    model_unexp_type_err = copy.deepcopy(model_json)
    model_unexp_type_err['id'] = '701'
    assert mdef.validate_attrs(model_unexp_type_err, mdef.model_attr)[0:15] == 'Unexpected type'

    model_attr_missing = copy.deepcopy(model_json)
    del model_attr_missing['id']
    assert mdef.validate_attrs(model_attr_missing, mdef.model_attr)[0:27] == 'Mandatory attribute missing'

    # group
    assert mdef.validate_attrs(model_json['group'], mdef.group_attr) == ''
    group_unexp_attr_err = copy.deepcopy(model_json)['group']
    group_unexp_attr_err['abc'] = 'def'
    assert mdef.validate_attrs(group_unexp_attr_err, mdef.group_attr)[0:37] == 'Unexpected model definition attribute'

    group_unexp_type_err = copy.deepcopy(model_json)['group']
    group_unexp_type_err['name'] = 1
    assert mdef.validate_attrs(group_unexp_type_err, mdef.group_attr)[0:15] == 'Unexpected type'

    group_attr_missing = copy.deepcopy(model_json)['group']
    del group_attr_missing['name']
    assert mdef.validate_attrs(group_attr_missing, mdef.group_attr)[0:27] == 'Mandatory attribute missing'

    # point
    assert mdef.validate_attrs(model_json['group']['points'][0], mdef.point_attr) == ''

    point_unexp_attr_err = copy.deepcopy(model_json)['group']['points'][0]
    point_unexp_attr_err['abc'] = 'def'
    assert mdef.validate_attrs(point_unexp_attr_err, mdef.point_attr)[0:37] == 'Unexpected model definition attribute'

    point_unexp_type_err = copy.deepcopy(model_json)['group']['points'][0]
    point_unexp_type_err['name'] = 1
    assert mdef.validate_attrs(point_unexp_type_err, mdef.point_attr)[0:15] == 'Unexpected type'

    point_unexp_value_err = copy.deepcopy(model_json)['group']['points'][1]
    point_unexp_value_err['access'] = 'z'
    assert mdef.validate_attrs(point_unexp_value_err, mdef.point_attr)[0:16] == 'Unexpected value'

    point_attr_missing = copy.deepcopy(model_json)['group']['points'][0]
    del point_attr_missing['name']
    assert mdef.validate_attrs(point_attr_missing, mdef.point_attr)[0:27] == 'Mandatory attribute missing'

    # symbol
    assert mdef.validate_attrs(model_json['group']['points'][2]['symbols'][0], mdef.symbol_attr) == ''

    symbol_unexp_attr_err = copy.deepcopy(model_json)['group']['points'][2]['symbols'][0]
    symbol_unexp_attr_err['abc'] = 'def'
    assert mdef.validate_attrs(symbol_unexp_attr_err, mdef.symbol_attr)[0:37] == 'Unexpected model definition attribute'

    symbol_unexp_type_err = copy.deepcopy(model_json)['group']['points'][2]['symbols'][0]
    symbol_unexp_type_err['name'] = 1
    assert mdef.validate_attrs(symbol_unexp_type_err, mdef.symbol_attr)[0:15] == 'Unexpected type'

    symbol_attr_missing = copy.deepcopy(model_json)['group']['points'][2]['symbols'][0]
    del symbol_attr_missing['name']
    assert mdef.validate_attrs(symbol_attr_missing, mdef.symbol_attr)[0:27] == 'Mandatory attribute missing'


def test_validate_group_point_dup():
    with open('./sunspec2/models/json/model_704.json') as f:
        model_json = json.load(f)

    assert mdef.validate_group_point_dup(model_json['group']) == ''

    dup_group_id_model = copy.deepcopy(model_json)
    dup_group_id_group = dup_group_id_model['group']
    dup_group_id_group['groups'][0]['name'] = 'PFWInjRvrt'
    assert mdef.validate_group_point_dup(dup_group_id_group)[0:18] == 'Duplicate group id'

    dup_group_point_id_model = copy.deepcopy(model_json)
    dup_group_point_id_group = dup_group_point_id_model['group']
    dup_group_point_id_group['groups'][0]['name'] = 'PFWInjEna'
    assert mdef.validate_group_point_dup(dup_group_point_id_group)[0:28] == 'Duplicate group and point id'

    mand_attr_miss_model = copy.deepcopy(model_json)
    mand_attr_miss_group = mand_attr_miss_model['group']
    del mand_attr_miss_group['groups'][0]['name']
    assert mdef.validate_group_point_dup(mand_attr_miss_group)[0:32] == 'Mandatory name attribute missing'

    dup_point_id_model = copy.deepcopy(model_json)
    dup_point_id_group = dup_point_id_model['group']
    dup_point_id_group['points'][1]['name'] = 'ID'
    assert mdef.validate_group_point_dup(dup_point_id_group)[0:30] == 'Duplicate point id ID in group'

    mand_attr_miss_point_model = copy.deepcopy(model_json)
    mand_attr_miss_point_group = mand_attr_miss_point_model['group']
    del mand_attr_miss_point_group['points'][1]['name']
    assert mdef.validate_group_point_dup(mand_attr_miss_point_group)[0:55] == 'Mandatory attribute missing in point ' \
                                                                              'definition element'


def test_validate_symbols():
    symbols = [
        {'name': 'CAT_A', 'value': 1},
        {'name': 'CAT_B', 'value': 2}
    ]
    assert mdef.validate_symbols(symbols, mdef.symbol_attr) == ''


def test_validate_sf():
    with open('./sunspec2/models/json/model_702.json') as f:
        model_json = json.load(f)

    model_point = model_json['group']['points'][2]
    model_group = model_json['group']
    model_group_arr = [model_group, model_group]
    assert mdef.validate_sf(model_point, 'W_SF', model_group_arr) == ''

    not_sf_type_model = copy.deepcopy(model_json)
    not_sf_type_point = not_sf_type_model['group']['points'][2]
    not_sf_type_group = not_sf_type_model['group']
    not_sf_type_group_arr = [not_sf_type_group, not_sf_type_group]
    for point in not_sf_type_model['group']['points']:
        if point['name'] == 'W_SF':
            point['type'] = 'abc'
    assert mdef.validate_sf(not_sf_type_point, 'W_SF', not_sf_type_group_arr)[0:60] == 'Scale factor W_SF for point ' \
                                                                                       'WMaxRtg is not scale factor ' \
                                                                                       'type'

    sf_not_found_model = copy.deepcopy(model_json)
    sf_not_found_point = sf_not_found_model['group']['points'][2]
    sf_not_found_group = sf_not_found_model['group']
    sf_not_found_group_arr = [sf_not_found_group, sf_not_found_group]
    assert mdef.validate_sf(sf_not_found_point, 'ABC', sf_not_found_group_arr)[0:44] == 'Scale factor ABC for point ' \
                                                                                        'WMaxRtg not found'

    sf_out_range_model = copy.deepcopy(model_json)
    sf_out_range_point = sf_out_range_model['group']['points'][2]
    sf_out_range_group = sf_out_range_model['group']
    sf_out_range_group_arr = [sf_out_range_group, sf_out_range_group]
    assert mdef.validate_sf(sf_out_range_point, 11, sf_out_range_group_arr)[0:46] == 'Scale factor 11 for point ' \
                                                                                     'WMaxRtg out of range'

    sf_invalid_type_model = copy.deepcopy(model_json)
    sf_invalid_type_point = sf_invalid_type_model['group']['points'][2]
    sf_invalid_type_group = sf_invalid_type_model['group']
    sf_invalid_type_group_arr = [sf_invalid_type_group, sf_invalid_type_group]
    assert mdef.validate_sf(sf_invalid_type_point, 4.0, sf_invalid_type_group_arr)[0:51] == 'Scale factor 4.0 for' \
                                                                                            ' point WMaxRtg has ' \
                                                                                            'invalid type'


def test_validate_point_def():
    with open('./sunspec2/models/json/model_702.json') as f:
        model_json = json.load(f)

    model_group = model_json['group']
    group = model_json['group']
    point = model_json['group']['points'][0]
    assert mdef.validate_point_def(point, model_group, group) == ''

    unk_point_type_model = copy.deepcopy(model_json)
    unk_point_type_model_group = unk_point_type_model['group']
    unk_point_type_group = unk_point_type_model['group']
    unk_point_type_point = unk_point_type_model['group']['points'][0]
    unk_point_type_point['type'] = 'abc'
    assert mdef.validate_point_def(unk_point_type_point, unk_point_type_model_group,
                                   unk_point_type_group)[0:35] == 'Unknown point type abc for point ID'

    dup_symbol_model = copy.deepcopy(model_json)
    dup_symbol_model_group = dup_symbol_model['group']
    dup_symbol_group = dup_symbol_model['group']
    dup_symbol_point = dup_symbol_model['group']['points'][21]
    dup_symbol_point['symbols'][0]['name'] = 'CAT_B'
    assert mdef.validate_point_def(dup_symbol_point, dup_symbol_model_group,
                                   dup_symbol_group)[0:19] == 'Duplicate symbol id'

    mand_attr_missing = copy.deepcopy(model_json)
    mand_attr_missing_model_group = mand_attr_missing['group']
    mand_attr_missing_group = mand_attr_missing['group']
    mand_attr_missing_point = mand_attr_missing['group']['points'][0]
    del mand_attr_missing_point['name']
    assert mdef.validate_point_def(mand_attr_missing_point, mand_attr_missing_model_group,
                                   mand_attr_missing_group)[0:27] == 'Mandatory attribute missing'


def test_validate_group_def():
    with open('./sunspec2/models/json/model_702.json') as f:
        model_json = json.load(f)

    assert mdef.validate_group_def(model_json['group'], model_json['group']) == ''


def test_validate_model_group_def():
    with open('./sunspec2/models/json/model_702.json') as f:
        model_json = json.load(f)

    assert mdef.validate_model_group_def(model_json, model_json['group']) == ''

    missing_id_model = copy.deepcopy(model_json)
    missing_id_group = missing_id_model['group']
    missing_id_group['points'][0]['name'] = 'abc'
    assert mdef.validate_model_group_def(missing_id_model, missing_id_group)[0:41] == 'First point in top-level' \
                                                                                      ' group must be ID'

    wrong_model_id_model = copy.deepcopy(model_json)
    wrong_model_id_group = wrong_model_id_model['group']
    wrong_model_id_group['points'][0]['value'] = 0
    assert mdef.validate_model_group_def(wrong_model_id_model, wrong_model_id_group)[0:42] == 'Model ID does not ' \
                                                                                              'match top-level group ID'

    missing_len_model = copy.deepcopy(model_json)
    missing_len_group = missing_len_model['group']
    missing_len_group['points'][1]['name'] = 'abc'
    assert mdef.validate_model_group_def(missing_len_model, missing_len_group)[0:41] == 'Second point in top-level ' \
                                                                                        'group must be L'

    missing_two_p_model = copy.deepcopy(model_json)
    missing_two_p_group = missing_two_p_model['group']
    missing_two_p_point = missing_two_p_group['points'][0]
    del missing_two_p_group['points']
    missing_two_p_group['points'] = [missing_two_p_point]
    assert mdef.validate_model_group_def(missing_two_p_model, missing_two_p_group)[0:48] == 'Top-level group must' \
                                                                                            ' contain at least two ' \
                                                                                            'points'

    missing_p_def_model = copy.deepcopy(model_json)
    missing_p_def_group = missing_p_def_model['group']
    del missing_p_def_group['points']
    assert mdef.validate_model_group_def(missing_p_def_model, missing_p_def_group)[0:41] == 'Top-level group' \
                                                                                            ' missing point definitions'


def test_validate_model_def():
    with open('./sunspec2/models/json/model_702.json') as f:
        model_json = json.load(f)

    assert mdef.validate_model_def(model_json) == ''


def test_from_json_str():
    with open('./sunspec2/models/json/model_63001.json') as f:
        model_json = json.load(f)
        model_json_str = json.dumps(model_json)
        assert isinstance(mdef.from_json_str(model_json_str), dict)


def test_from_json_file():
    assert isinstance(mdef.from_json_file('./sunspec2/models/json/model_63001.json'), dict)


def test_to_json_str():
    with open('./sunspec2/models/json/model_63001.json') as f:
        model_json = json.load(f)
        assert isinstance(mdef.to_json_str(model_json), str)


def test_to_json_filename():
    assert mdef.to_json_filename('63001') == 'model_63001.json'


def test_to_json_file(tmp_path):
    with open('./sunspec2/models/json/model_63001.json') as f:
        model_json = json.load(f)
        mdef.to_json_file(model_json, filedir=tmp_path)

    with open(tmp_path / 'model_63001.json') as f:
        model_json = json.load(f)
        assert isinstance(model_json, dict)


def test_model_filename_to_id():
    assert mdef.model_filename_to_id('model_00077.json') == 77
    with pytest.raises(Exception) as exc:
        mdef.model_filename_to_id('model_abc.json')
    assert 'Error extracting model id from filename' in str(exc.value)
