#!/usr/bin/env python
# encoding: utf-8
#
# This file is a part of the CaosDB Project.
#
# Copyright (C) 2022 IndiScale GmbH <info@indiscale.com>
# Copyright (C) 2021 University Medical Center Göttingen, Institute for Medical Informatics
# Copyright (C) 2021 Florian Spreckelsen <florian.spreckelsen@med.uni-goettingen.de>
# Copyright (C) 2022 Florian Spreckelsen <f.spreckelsen@indiscale.com>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.
"""Integration tests for the `assure_...` functions from
`caosadvancedtools.cfood`. They mainly test the in-place updates when
no `to_be_updated` is specified.

"""
import caosdb as db

from caosadvancedtools.cfood import (assure_object_is_in_list)
from caosadvancedtools.guard import (global_guard, RETRIEVE, UPDATE)


def setup():
    """Delete all test entities."""
    db.execute_query("FIND Test*").delete(raise_exception_on_error=False)


def setup_module():
    """Allow all updates and delete test data"""
    global_guard.level = UPDATE
    setup()


def teardown_module():
    """Reset guard level and delete test data."""
    global_guard.level = RETRIEVE
    setup()


def test_assure_list_in_place():
    """Test an in-place update with `assure_object_is_in_list`."""
    int_list_prop = db.Property(name="TestIntListProperty",
                                datatype=db.LIST(db.INTEGER)).insert()
    rt1 = db.RecordType(name="TestType1").add_property(
        name=int_list_prop.name).insert()
    rec1 = db.Record(name="TestRecord1").add_parent(rt1)
    rec1.add_property(name=int_list_prop.name, value=[1]).insert()

    # Nothing should happen:
    assure_object_is_in_list(1, rec1, int_list_prop.name, to_be_updated=None)
    assert len(rec1.get_property(int_list_prop.name).value) == 1
    assert 1 in rec1.get_property(int_list_prop.name).value

    # Insertion should happen in-place
    assure_object_is_in_list(2, rec1, int_list_prop.name, to_be_updated=None)
    assert len(rec1.get_property(int_list_prop.name).value) == 2
    assert 2 in rec1.get_property(int_list_prop.name).value

    # Better safe than sorry -- test for reference properties, too.
    ref_rt = db.RecordType(name="TestRefType").insert()
    ref_rec1 = db.Record(name="TestRefRec1").add_parent(ref_rt).insert()
    ref_rec2 = db.Record(name="TestRefRec2").add_parent(ref_rt).insert()
    ref_rec3 = db.Record(name="TestRefRec3").add_parent(ref_rt).insert()
    rt2 = db.RecordType(name="TestType2").add_property(
        name=ref_rt.name, datatype=db.LIST(ref_rt.name)).insert()
    rec2 = db.Record(name="TestRecord2").add_parent(rt2)
    rec2.add_property(name=ref_rt.name, value=[ref_rec1],
                      datatype=db.LIST(ref_rt.name)).insert()

    # Again, nothing should happen
    assure_object_is_in_list(ref_rec1, rec2, ref_rt.name, to_be_updated=None)
    assert len(rec2.get_property(ref_rt.name).value) == 1
    assert ref_rec1.id in rec2.get_property(ref_rt.name).value

    # In-place update with two additional references
    assure_object_is_in_list([ref_rec2, ref_rec3],
                             rec2, ref_rt.name, to_be_updated=None)
    assert len(rec2.get_property(ref_rt.name).value) == 3
    assert ref_rec2.id in rec2.get_property(ref_rt.name).value
    assert ref_rec3.id in rec2.get_property(ref_rt.name).value


def test_add_to_empty_list():
    """See https://gitlab.com/caosdb/caosdb-advanced-user-tools/-/issues/40."""
    # @author Florian Spreckelsen
    # @date 2022-04-19
    referenced_rt = db.RecordType(name="TestReferencedType").insert()
    list_prop = db.Property(name="TestListProp",
                            datatype=db.LIST(referenced_rt)).insert()
    referencing_rt = db.RecordType(
        name="TestReferencingType").add_property(list_prop).insert()

    db.Record(name="TestReferencedRecord").add_parent(referenced_rt).insert()
    db.Record(name="TestReferencingRecord").add_parent(
        referencing_rt).add_property(list_prop, value=[]).insert()

    referenced_rec = db.execute_query("FIND TestReferencedRecord", unique=True)
    referencing_rec = db.execute_query(
        "FIND TestReferencingRecord", unique=True)

    assure_object_is_in_list(referenced_rec, referencing_rec, list_prop.name)

    referencing_rec = db.execute_query(
        "FIND TestReferencingRecord", unique=True)
    assert referencing_rec.get_property(list_prop.name).value == [
        referenced_rec.id]
