from __future__ import unicode_literals

import unittest

import nose
from parameterized import parameterized

from ldclient.models import (
    Assay, AssayDefaultAggregationRule, AssayFolderRule, AssayType, ColorStyle, ColumnarLigandDesignerConfiguration,
    ColumnAlias, CompoundLink, Database, DatabaseColumn, DataSourceAssayFolderRules, DecimalFormattingRule,
    Folder, Formula, FreeformColumn, LiveReport, LiveReportMetadata, Model, ModelRecursive,
    ModelReturn, ModelTemplateVar, MPO, Project, Rationale, UsernameToProjectIds, ViewMap)

from ldclient.api.requester import clean_host_name
import ldclient.enums


class SessionApiClientUnitTest(unittest.TestCase):

    @parameterized.expand([
        ["missing https", "localhost:8090/livedesign/api", "https://localhost:8090/livedesign/api"],
        [
            "specified http", "http://maestro-export.dev-75.bb.schrodinger.com/livedesign/api",
            "http://maestro-export.dev-75.bb.schrodinger.com/livedesign/api"
        ],
        [
            "trailing slash", "https://example.com/livedesign/api/",
            "https://example.com/livedesign/api"
        ],
        ["missing /livedesign/api", "https://example.com", "https://example.com/livedesign/api"],
        [
            "missing /api", "https://example.com:80/livedesign/",
            "https://example.com:80/livedesign/api"
        ],
    ])
    def test_clean_host_name(self, test_name, input_host, expected):
        self.assertEqual(clean_host_name(input_host), expected)


class LDModelsUnitTests(unittest.TestCase):
    maxDiff = None

    rationale_def = {
        'description': 'my_description',
        'user_name': 'testuser',
        'corporate_id': 'CRA-12345',
        'live_report_id': '12345',
        'default': False,
        'id': '54321',
        'created_at': '',
        'updated_at': ''
    }

    template_var_string_def = {
        'name': 'test string',
        'data': 'ABC',
        'type': 'string',
        'tag': 'read_only',
        'is_optional': False,
        'optional_parameter_name': '',
        'id': None
    }

    template_var_file_def = {
        'name': 'python script',
        'data': 'attachment_uuid',
        'type': 'file',
        'tag': 'default',
        'is_optional': False,
        'optional_parameter_name': '',
        'id': None
    }

    returns_def = {
        'id': None,
        'key': 'P2',
        'type': None,
        'units': 'A',
        'display_name': 'String Value',
        'precision': 1,
        'addable_column_id': None,
        'tag': 'DEFAULT'
    }

    assay_type_def = {
        'name': '%Control',
        'count': 6,
        'addable_column_id': 1,
        'value_type': None,
        'column_type': 'ASSAY',
        'project_ids': [0, 1, 2]
    }

    model_def_for_as_merged = {
        'id': None,
        'name': 'name',
        'description': 'test python script',
        'user': 'demo',
        'archived': False,
        'parent': 2,
        'template_vars': [template_var_string_def],
        'returns': [returns_def],
        'batch_size': None,
        'command_queue': None,
        'command_type': None,
        'commands': [],
        'created_at': None,
        'project_ids': ['0'],
        'published': True,
        'updated_at': None,
        'folder': 'User Defined/whee',
        'automatic_rerun': True,
        'as_merged': None
    }

    model_def = {
        'id': None,
        'name': 'name',
        'description': 'test python script',
        'user': 'demo',
        'archived': False,
        'parent': 2,
        'template_vars': [template_var_string_def],
        'returns': [returns_def],
        'batch_size': None,
        'command_queue': None,
        'command_type': None,
        'commands': [],
        'created_at': None,
        'project_ids': ['0'],
        'published': True,
        'updated_at': None,
        'folder': 'User Defined/whee',
        'automatic_rerun': True,
        'as_merged': model_def_for_as_merged
    }

    color_styles_def = {
        'color_high': -39322,
        'color_low': -10027162,
        'range_low': 0.02,
        'range_high': 30.2,
        'value_low': 0.02,
        'value_high': 30.2,
        'log_scale': False,
        'matching_string': None,
        'relative': False,
        'color_style_type': ldclient.enums.ColorStyleType.NUMBER
    }

    def test_ldmodel_serialization_basic(self):
        """
        Tests basic serialization.

        All fields map directly to basic type variables in the object
        """

        rationale_obj = Rationale.from_dict(self.rationale_def)

        self.assertDictEqual(self.rationale_def, rationale_obj.as_dict())

    def test_ldmodel_serialization_defaults(self):
        """
        Tests serialization with initialization defaults
        """

        assay_type_def = {
            'name': 'my_assay_type',
            'addable_column_id': 0,
            'value_type': None,
            'column_type': None,
            'project_ids': None
        }
        assay_type_obj = AssayType.from_dict(assay_type_def)
        assay_type_def['count'] = None

        self.assertDictEqual(assay_type_def, assay_type_obj.as_dict())

    def test_ldmodel_serialization_object_list(self):
        """
        Tests serialization when some fields are other objects
        """

        assay_type_obj = AssayType.from_dict(self.assay_type_def)

        self.assertDictEqual(self.assay_type_def, assay_type_obj.as_dict())

    def test_ldmodel_project(self):
        """
        Tests: models.Project
        """
        project_def = {
            'id': '1',
            'name': 'CMET',
            'active': 'Y',
            'alternate_id': 'CMET',
            'default_template_id': 0,
            'description': 'CMET',
            'restricted': False,
            'keep_styles': False,
            'therapeutic_area': None,
            'project_lead': None
        }

        project_obj = Project.from_dict(project_def)
        self.assertDictEqual(project_def, project_obj.as_dict())

    def test_ldmodel_assay_type(self):
        """
        Tests: models.AssayType
        """

        assay_type_obj = AssayType.from_dict(self.assay_type_def)

        self.assertDictEqual(self.assay_type_def, assay_type_obj.as_dict())

    def test_ldmodel_rationale(self):
        """
        Tests:  models.Rationale
        """

        rationale_obj = Rationale.from_dict(self.rationale_def)

        self.assertDictEqual(self.rationale_def, rationale_obj.as_dict())

    def test_ldmodel_database(self):
        """
        Tests: models.Database
        """

        database_def = {
            'id': None,
            'name': 'pri',
            'display_name': 'Pri',
            'projects': ['0'],
            'primary': True,
            'writeback': True,
            'ideas': True,
        }

        database_obj = Database.from_dict(database_def)

        self.assertDictEqual(database_def, database_obj.as_dict())

    def test_ldmodel_template_var(self):
        """
        Tests: models.ModelTemplateVar
        """

        template_var_string_value_obj = ModelTemplateVar.from_dict(self.template_var_string_def)
        self.assertDictEqual(self.template_var_string_def, template_var_string_value_obj.as_dict())

    def test_ldmodel_returns(self):
        """
        Tests: models.ModelReturn
        """

        returns_obj = ModelReturn.from_dict(self.returns_def)
        self.assertDictEqual(self.returns_def, returns_obj.as_dict())

    def test_ldmodel_file_macro_value(self):
        """
        Tests: models.FileMacroValue
        """

        file_macro_value_obj = ModelTemplateVar.from_dict(self.template_var_file_def)
        self.assertDictEqual(self.template_var_file_def, file_macro_value_obj.as_dict())

    def test_ldmodel_folder(self):
        folder_def = {'id': None, 'name': 'test', 'project_id': 0}
        folder_obj = Folder.from_dict(folder_def)
        self.assertDictEqual(folder_def, folder_obj.as_dict())

    def test_ldmodel_protocol(self):
        """
        Tests: models.Protocols
        """
        pre_cmd = '/mnt/schrodinger/utilities/sdconvert -isd ${INPUT_SDFILE} -omae input.mae'
        cmd = '/mnt/schrodinger/run python ${python script:FILE-INPUT} input.mae ${test string:TEXT-INPUT} > out.txt\n'
        post_cmd = 'cat out.txt'
        commands = [{
            'command': pre_cmd,
            'driver_id': 1,
            'id': None
        }, {
            'command': cmd,
            'driver_id': 1,
            'id': None
        }, {
            'command': post_cmd,
            'driver_id': 1,
            'id': None
        }]
        template_vars = [self.template_var_string_def, self.template_var_file_def]
        protocol_def = {
            'id': None,
            'parent': None,
            'name': 'generic python test',
            'archived': False,
            'published': True,
            'user': 'demo',
            'description': 'run a python test script',
            'batch_size': {
                'value': 100,
                'tag': 'DEFAULT'
            },
            'folder': 'User Defined/blah',
            'template_vars': template_vars,
            "commands": commands,
            'returns': [],
            'project_ids': ['0'],
            'created_at': None,
            'updated_at': None,
            'command_type': None,
            'command_queue': None,
            'automatic_rerun': True,
            'as_merged': self.model_def_for_as_merged
        }
        protocol_obj = Model.from_dict(protocol_def)
        self.assertDictEqual(protocol_def, protocol_obj.as_dict())

    def test_color_styles(self):
        """
        Tests: models.ColorStyle:
        """
        color_styles_obj = ColorStyle.from_dict(self.color_styles_def)
        self.assertDictEqual(self.color_styles_def, color_styles_obj.as_dict())

    def test_ldmodel_viewmap(self):
        """
        Tests:
        models.ColorStyle
        models.ViewMap
        """
        color_styles_obj = ColorStyle.from_dict(self.color_styles_def)

        view_map_def = {'color_styles': [self.color_styles_def]}

        view_map = ViewMap(color_styles=[color_styles_obj])
        view_map_json = view_map.as_json()

        self.assertDictEqual(view_map_def, ViewMap.from_json(view_map_json).as_dict())

    def test_ldmodel_model(self):
        """
        Tests:
        models.ModelTemplateVar
        models.ModelReturn
        models.DefaultVersion
        """

        text_macro_value_obj = ModelTemplateVar.from_dict(self.template_var_string_def)
        returns_obj = ModelReturn.from_dict(self.returns_def)
        as_merged_obj = Model.from_dict(self.model_def_for_as_merged)

        model = Model(
            id=None,
            parent=2,
            name='name',
            archived=False,
            published=True,
            user='demo',
            folder='User Defined/whee',
            returns=[returns_obj],
            project_ids=[0],
            template_vars=[text_macro_value_obj],
            description='test python script',
            commands=[],
            automatic_rerun=True,
            as_merged=as_merged_obj)
        model_json = model.as_json()
        actual = Model.from_json(model_json).as_dict()
        self.assertDictEqual(self.model_def, actual)

    def test_ldmodel_model2(self):
        """
        Tests:
        models.TextMacroValue
        models.ModelReturn
        models.DefaultVersion
        models.model
        """

        model_ref = {
            'parent': None,
            'name': 'test',
            'archived': False,
            'published': True,
            'user': 'demo',
            'folder': 'User Defined/demo',
            'returns': [],
            'project_ids': ['0'],
            'template_vars': [],
            'description': 'description',
            'commands': [],
            'batch_size': {
                'tag': 'DEFAULT',
                'value': 10
            },
            'command_type': {
                'tag': 'DEFAULT',
                'value': 'REALTIME'
            },
            'command_queue': {
                'tag': 'DEFAULT',
                'value': 'NORMAL'
            },
            'automatic_rerun': True,
            'as_merged': self.model_def_for_as_merged,
            'created_at': None,
            'updated_at': None,
            'id': None
        }

        model = Model(
            parent=None,
            name='test',
            archived=False,
            published=True,
            user='demo',
            folder='User Defined/demo',
            returns=[],
            project_ids=['0'],
            template_vars=[],
            description='description',
            commands=[],
            batch_size=ModelRecursive('DEFAULT', 10),
            command_type=ModelRecursive('DEFAULT', 'REALTIME'),
            command_queue=ModelRecursive('DEFAULT', 'NORMAL'),
            id=None,
            automatic_rerun=True,
            as_merged=Model.from_dict(self.model_def_for_as_merged))
        model_json = model.as_json()
        self.assertDictEqual(model_ref, Model.from_json(model_json).as_dict())

    def test_ldmodel_assay(self):
        """
        Tests:
        models.AssayType
        models.Assay
        """

        assay_type_obj = AssayType.from_dict(self.assay_type_def)

        assay_ref = {
            'protocol': None,
            'name': 'AMBIT-TNNI3K',
            'database_name': 'pri',
            'project_ids': ['0'],
            'units': None,
            'lot': None,
            'concentration_units': None,
            'concentration': None,
            'types': [self.assay_type_def],
            'folder_path': 'OutSourced'
        }

        assay = Assay(
            protocol=None,
            name='AMBIT-TNNI3K',
            database_name='pri',
            project_ids=['0'],
            units=None,
            lot=None,
            concentration_units=None,
            concentration=None,
            types=[assay_type_obj],
            folder_path='OutSourced')

        assay_json = assay.as_json()

        self.assertDictEqual(assay_ref, Assay.from_json(assay_json).as_dict())

    def test_ldmodel_live_report(self):
        """
        Tests:
        models.LiveReport
        models.Rationale
        """

        live_report_def = {
            "id": None,
            "title": "asdf",
            "description": "",
            "owner": "",
            "template": False,
            "is_private": False,
            "default_rationale": "Default rationale description",
            "active": False,
            "project_id": "0",
            "update_policy": "by_cachebuilder",
            "type": "compound",
            "shared_editable": True,
            "tags": [],
            "last_saved_date": None,
            "sorted_columns": [],
            "hidden_rows": [],
            "frozen_rows": [],
            "addable_columns": ["1", "2"],
            "scaffolds": [],
            "report_level": "parent"
        }

        live_report_obj = LiveReport.from_dict(live_report_def)
        self.assertDictEqual(live_report_def, live_report_obj.as_dict())

    def test_ldmodel_live_report_metadata(self):
        """
        Tests:
        models.LiveReport
        models.Rationale
        """

        live_report_metadata_def = {
            "id": None,
            "title": "asdf",
            "description": "",
            "owner": "",
            "template": False,
            "is_private": False,
            "active": False,
            "project_id": "0",
            "update_policy": "by_cachebuilder",
            "type": "compound",
            "shared_editable": True,
            "tags": [],
            "last_saved_date": None,
            "report_level": "parent"
        }

        live_report_metadata_obj = LiveReportMetadata.from_dict(live_report_metadata_def)
        self.assertDictEqual(live_report_metadata_def, live_report_metadata_obj.as_dict())

    def test_ldmodel_database_column(self):
        """
        Tests:
        models.DatabaseColumn
        """

        database_column_metadata_def = {
            "id": 123,
            "name": "Lot Scientist",
            "addable_column_id": 2345,
            "value_type": "string",
            "folder_path": "Lot/Other"
        }
        database_column_metadata_obj = DatabaseColumn.from_dict(database_column_metadata_def)
        self.assertDictEqual(database_column_metadata_def, database_column_metadata_obj.as_dict())

    def test_ldmodel_as_merged(self):
        """
        Tests as_merged field
        """
        as_merged_model_obj = Model.from_dict(self.model_def_for_as_merged)
        self.assertDictEqual(as_merged_model_obj.as_dict(), self.model_def_for_as_merged)

    def test_create_freeform_column(self):
        create_freeform_column_def = {
            'id': None,
            'name': "An FFC",
            'description': "I am an FFC",
            'type': FreeformColumn.COLUMN_TEXT,
            'value_type': FreeformColumn.VALUE_STRING,
            'published': True,
            'live_report_id': None,
            'project_id': "0",
            'picklist': False,
            'values': [],
            'multiple_values_allowed': False,
            'read_only': False,
            'edit_access_users': [],
        }

        freeform_column_obj = FreeformColumn.from_dict(create_freeform_column_def)
        self.assertDictEqual(create_freeform_column_def, freeform_column_obj.as_dict())

    def testCompoundLink(self):
        observation_def = {'id': '1', 'real_entity_id': '25', 'virtual_entity_id': '17'}
        compound_link_observation = CompoundLink.from_dict(observation_def)
        self.assertDictEqual(observation_def, compound_link_observation.as_dict())

    def testAssayFolderRule(self):
        assay_folder_rule_def = {
            "id": "1",
            "comparison_type": "STARTS_WITH",
            "folder": "/Assay/PK/Mouse",
            "match_operand": "MOUSE",
            "match_type": "STRING"
        }

        assay_folder_rule_obj = AssayFolderRule.from_dict(assay_folder_rule_def)
        self.assertDictEqual(assay_folder_rule_def, assay_folder_rule_obj.as_dict())

    def testDataSourceAssayFolderRules(self):
        data_source_assay_folder_rules_def = {
            "id": "1",
            "data_source_id": "3",
            "rules": [{
                "id": "1",
                "comparison_type": "STARTS_WITH",
                "folder": "/Assay/PK/Mouse",
                "match_operand": "MOUSE",
                "match_type": "STRING"
            },
                {
                    "id": "2",
                    "comparison_type": "STARTS_WITH",
                    "folder": "/Assay/PK/Old",
                    "match_operand": "",
                    "match_type": "STRING"
            }]
        }

        data_source_assay_folder_rules_obj = DataSourceAssayFolderRules.from_dict(
            data_source_assay_folder_rules_def)
        self.assertDictEqual(data_source_assay_folder_rules_def,
                             data_source_assay_folder_rules_obj.as_dict())

    def test_column_alias(self):
        column_alias_def = {
            "id": "1",
            "alias": "abc",
            "addable_column_id": "123",
            "live_report_id": "864",
            "project_id": "0"
        }

        column_alias_obj = ColumnAlias.from_dict(column_alias_def)
        self.assertDictEqual(column_alias_def, column_alias_obj.as_dict())

    def test_assay_default_aggregation_rule(self):
        assay_default_aggregation_rule_def = {
            "id": 1,
            "match_target": "ASSAY_RESULT_TYPE",
            "match_operator": "EQUALS",
            "match_operand": "Ki",
            "method": "min",
            "exclude_operators": True,
        }

        assay_default_aggregation_rule_obj = AssayDefaultAggregationRule.from_dict(
            assay_default_aggregation_rule_def)
        self.assertDictEqual(assay_default_aggregation_rule_def,
                             assay_default_aggregation_rule_obj.as_dict())

    def test_results_metadata_conversions(self):
        live_report_results_metadata_def = {
            "version": "1",
            "minor_version": "1",
            "experiment_addable_column_id": "1",
            "fulfills_promise_id": "1",
            "pending_promise_ids": ["1", "2", "3"]
        }
        live_report_results_metadata_def['version'] = int(
            live_report_results_metadata_def['version'])
        live_report_results_metadata_def['minor_version'] = int(
            live_report_results_metadata_def['minor_version'])
        live_report_results_metadata_def['fulfills_promise_id'] = int(
            live_report_results_metadata_def['fulfills_promise_id'])
        live_report_results_metadata_def['experiment_addable_column_id'] = int(
            live_report_results_metadata_def['experiment_addable_column_id'])
        live_report_results_metadata_def['pending_promise_ids'] = [
            int(id) for id in live_report_results_metadata_def['pending_promise_ids']
        ]

        self.assertEqual(live_report_results_metadata_def['version'], 1)
        self.assertEqual(live_report_results_metadata_def['minor_version'], 1)
        self.assertEqual(live_report_results_metadata_def['experiment_addable_column_id'], 1)
        self.assertEqual(live_report_results_metadata_def['fulfills_promise_id'], 1)
        self.assertEqual(live_report_results_metadata_def['pending_promise_ids'], [1, 2, 3])

    def test_decimal_formatting_rule(self):
        decimal_formatting_rule_def = {
            "name": "df7",
            "value": "0.00##",
            "default_value": "0.0#",
        }

        decimal_formatting_rule_obj = DecimalFormattingRule.from_dict(decimal_formatting_rule_def)
        self.assertDictEqual(decimal_formatting_rule_def, decimal_formatting_rule_obj.as_dict())

    def test_username_to_project_ids(self):
        username_to_project_ids_def = {"username": "xyz", "project_ids": ["id1", "id2", "id3"]}

        username_to_project_ids_obj = UsernameToProjectIds.from_dict(username_to_project_ids_def)
        self.assertDictEqual(username_to_project_ids_def, username_to_project_ids_obj.as_dict())

    def test_create_mpo(self):
        create_mpo_def = {
            "id": None,
            "addable_column_id": None,
            "name": "ADME profile",
            "description": "",
            "active": True,
            "only_calc_if_complete": False,
            "project_id": "0",
            "constituents": [
                {
                    "addable_column_id": "1392",
                    "addable_column_name": "QuickProps (PSA)",
                    "type": "bell",
                    "mono_threshold": None,
                    "bell_threshold": {
                        "low_threshold": 70.0,
                        "low_middle_threshold": 90.0,
                        "middle_high_threshold": 140.0,
                        "high_threshold": 160.0,
                        "middle_good": True
                    },
                    "category": None,
                    "weight": 1.0
                },  # yapf: disable
                {
                    "addable_column_id": "1396",
                    "addable_column_name": "QuickProps (AlogP)",
                    "type": "bell",
                    "mono_threshold": None,
                    "bell_threshold": {
                        "low_threshold": 1.0,
                        "low_middle_threshold": 2.0,
                        "middle_high_threshold": 5.0,
                        "high_threshold": 6.0,
                        "middle_good": True
                    },
                    "category": None,
                    "weight": 1.0
                },
                {
                    "addable_column_id": "1387",
                    "addable_column_name": "QuickProps (Mol. Wt.)",
                    "type": "mono",
                    "mono_threshold": {
                        "low_threshold": 500.0,
                        "high_threshold": 650.0,
                        "high_good": False
                    },
                    "bell_threshold": None,
                    "category": None,
                    "weight": 1.0
                },
                {
                    "addable_column_id": "4155",
                    "addable_column_name": "Adenosine Receptor (A1 Ki)",
                    "type": "category",
                    "mono_threshold": None,
                    "bell_threshold": None,
                    "category": {
                        "good": ["160"],
                        "ok": [],
                        "bad": ["4"]
                    },
                    "weight": 1.0
                }
            ],  # yapf: disable
            "creator": None
        }

        mpo_obj = MPO.from_dict(create_mpo_def)
        # Confirm constituents and their thresholds have been parsed properly
        self.assertEqual(mpo_obj.constituents[0].bell_threshold.low_middle_threshold, 90.0)
        self.assertEqual(mpo_obj.constituents[3].category.good[0], "160")

        # Do a round trip comparison
        self.assertDictEqual(create_mpo_def, mpo_obj.as_dict())

    def test_create_formula(self):
        create_formula_def = {
            "expression": "1.37*(-log($4154/1000000000))/$1394",
            "id": "37124",
            "name": "Ligand Efficiency",
            "project_id": "1351",
            "live_report_id": "55459",
            "value_type": "float",
            "published": False,
            "user": "demo",
            "created_at": 1567536599759,
            "updated_at": 1608136718432,
            "description": ""
        }

        formula_obj = Formula.from_dict(create_formula_def)

        # Do a round trip comparison
        self.assertDictEqual(create_formula_def, formula_obj.as_dict())

    def test_columnar_ligand_designer_configuration(self):
        columnar_ligand_designer_configuration_def = {
            "id": "1",
            "name": "Columnar Ligand Designer Configuration Name",
            "description": "Columnar Ligand Designer Configuration Description",
            "folder": "columnar/ligand/designer/configuration/folder",
            "addable_column_id": "2",
            "grid_file_attachment_id": "ab2341fdgf32323fr43ff",
            "protein_attachment_id": "gf32321fd433f4ffrab23",
            "reference_ligand_attachment_ids": ["sd22re5gh83sgg2fh73", "dgf62kdj266dvv972bch9"],
            "overlays": [
                {
                    "id": "2",
                    "type": "watermap",
                    "attachment_id": "fdjhdgj7374sdds3838dd"
                },
                {
                    "id": "3",
                    "type": "other",
                    "attachment_id": "fhfh484vcbf383jkd93df"
                }
            ],
            "project_ids": ["0", "1", "2", "3", "4"],
            "docking_precision": "HTVS",
            "user": "demo",
            "created_at": 238383393939,
            "updated_at": 238383394639
        }

        columnar_ligand_designer_configuration_obj = ColumnarLigandDesignerConfiguration.from_dict(columnar_ligand_designer_configuration_def)

        self.assertDictEqual(columnar_ligand_designer_configuration_def, columnar_ligand_designer_configuration_obj.as_dict())


if __name__ == "__main__":
    nose.run(argv=["", "ldclient_unit_tests", "-s", "--verbosity=2"])
