# -*- coding: utf-8 -*-
#
# ** header v3.0
# This file is a part of the CaosDB Project.
#
# Copyright (c) 2020 IndiScale GmbH <info@indiscale.com>
# Copyright (c) 2020 Daniel Hornung <d.hornung@indiscale.com>
# Copyright (c) 2020 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/>.
#
# ** end header

"""Tests for issues on gitlab.com, project caosdb-server."""

import os
import tempfile
import time

import caosdb as db
import pytest
from caosdb.exceptions import TransactionError


def setup_module():
    try:
        db.execute_query("FIND ENTITY WITH ID > 99").delete()
    except Exception as delete_exc:
        print(delete_exc)


def setup():
    """No setup required."""
    setup_module()


def teardown():
    """Deleting entities again."""
    setup_module()
    pass


# ########################### Issue tests start here #####################


def test_issue_39():
    """Query Language Bug - quotes around year

    Test for https://gitlab.com/caosdb/caosdb-server/-/issues/39
    """
    date_str = "2020-01-01"
    prop_name = "Test_Date"
    db.Property(name=prop_name, datatype=db.DATETIME).insert()
    db.RecordType(name="Test_Type").add_property(name=prop_name).insert()
    db.Record(name="Test_Record").add_parent(name="Test_Type").add_property(
        name=prop_name, value=date_str).insert()
    # Quotes around years should work in the query
    ent = db.execute_query("FIND entity WITH A Test_Date IN \"2020\"",
                           unique=True)
    assert ent.get_property(prop_name).value == date_str
    # This should raise a transaction error
    with pytest.raises(TransactionError):
        db.execute_query("FIND entity WITH A Test_Date IN \"abcd\"")


@pytest.mark.xfail(reason="to be fixed in server repo")
def test_issue_62():
    """datatype is not changed when recordtype name changes

    Tests for https://gitlab.com/caosdb/caosdb-server/-/issues/62
    """
    db.RecordType(name="Test_RTA").insert()
    db.Property(name="Test_Prop", datatype="Test_RTA").insert()
    db.Record(name="Test_Record").add_parent(
        name="Test_RTA").add_property(name="Test_Prop").insert()
    # rename Test_RTA to Test_RTB
    rtb = db.execute_query("FIND RecordType Test_RTA", unique=True)
    rtb.name = "Test_RTB"
    rtb.update()
    # renaming has to be reflected in Test_Record and Test_Prop
    rec = db.execute_query("FIND Record Test_Record", unique=True)
    assert rec.parents[0].name == rtb.name
    prop = db.execute_query("FIND Property Test_Prop", unique=True)
    assert prop.datatype == rtb.name  # fails; datatype not updated
    # Can't use Test_RTA as datatype anymore
    prop2 = db.Property(name="Test_Prop2", datatype="Test_RTA")
    with pytest.raises(TransactionError):
        prop2.insert()


def test_issue_85_a():
    """SQLIntegrityConstraintViolationException for special inheritance patterns.

    Tests for https://gitlab.com/caosdb/caosdb-server/-/issues/85
    """
    A = db.RecordType(name="A")
    B = db.RecordType(name="B")
    C = db.RecordType(name="C")

    B.add_parent(A)

    # This order is important for the test to fail.
    C.add_parent(B)
    C.add_parent(C)
    C.add_parent(A)

    c = db.Container()
    # c.extend([C, B, A])  # worked before #86 was fixed
    # c.extend([C, A, B])  # worked before #86 was fixed
    c.extend([B, C, A])    # insert() failed before #86 was fixed
    c.insert()  # Raised java.sql.SQLIntegrityConstraintViolationException:
    #           # Duplicate entry '12345-12346-12345' for key 'PRIMARY'


def test_issue_85_b():
    """SQLIntegrityConstraintViolationException for special inheritance patterns.

    Tests for https://gitlab.com/caosdb/caosdb-server/-/issues/85
    """
    A = db.RecordType(name="A")
    B = db.RecordType(name="B")
    C = db.RecordType(name="C")
    A.insert()
    B.insert()
    C.insert()
    B.add_parent(A)
    B.update()
    C.add_parent(B)
    C.update()
    C.add_parent(C)
    C.update()
    C.add_parent(A)
    C.update()  # Failed at this step


@pytest.mark.local_server
def test_issue_99():
    """Checksum updating failed with versioning enabled.
    """

    # Using files in extroot, because this allows us to update the file
    # content from the outside.
    local_dir = os.path.join(db.get_config().get("IntegrationTests",
                                                 "test_files.test_insert_files_in_dir.local"),
                             "test_issue_99")
    docker_dir = os.path.join(db.get_config().get("IntegrationTests",
                                                  "test_files.test_insert_files_in_dir.server"),
                              "test_issue_99")
    os.makedirs(local_dir, exist_ok=True)
    with tempfile.NamedTemporaryFile(dir=local_dir) as file_99:
        # Create File entity in CaosDB
        file_99.write("test 99\n".encode())
        os.fchmod(file_99.fileno(), 0o744)  # make the file world readable
        cont = db.Container()
        cont.insert(unique=False, raise_exception_on_error=False,
                    flags={"InsertFilesInDir": docker_dir})
        dbfile = cont[0]

        # Checksum should exist after a short time
        time.sleep(0.1)
        dbfile.retrieve()
        assert dbfile.checksum is not None


def test_issue_110():
    """query ignores ID: FIND MusicalInstrument which is referenced by Analysis with ID=124 """
    cont = db.Container()
    A = db.RecordType(name="TypeA")
    B = db.RecordType(name="TypeB")
    prop = db.Property(name="prop_ba", datatype=db.REFERENCE)

    # Referenced Records
    a1 = db.Record().add_parent(A)
    a2 = db.Record().add_parent(A)

    # Referencing Records
    b1 = db.Record().add_parent(B).add_property(prop, value=a1)
    b2 = db.Record().add_parent(B).add_property(prop, value=a2)

    cont.extend([A, B, prop, a1, a2, b1, b2])
    cont.insert()

    id_b1 = b1.id
    query = "FIND TypeA WHICH IS REFERENCED BY TypeB WITH ID={}".format(id_b1)
    print(query)
    result = db.execute_query(query)
    print(result)
    assert len(result) == 1
    print(result[0])
    print(a1)
    assert result[0].id == a1.id


def test_issue_120():
    """Editing entities that were created with a no longer existing user leads
    to a server error.

    The server should throw an error when CHECK_ENTITY_ACL_ROLES_MODE=MUST,
    otherwise a warning.
    """
    # insert an entity
    entity = db.RecordType("TestRT").insert(flags={"ACL": None})

    db.administration.set_server_property("CHECK_ENTITY_ACL_ROLES_MODE",
                                          "SHOULD")
    # update with non-existing user, realm and role
    entity.deny(
        realm="CaosDB",
        username="NON_EXISTING_USER",
        permission="USE:AS_REFERENCE")
    entity.update(flags={"ACL": None})
    assert entity.messages["Warning", 1104][0] == "User Role does not exist."

    entity.deny(
        realm="NON_EXISTING_REALM",
        username="NON_EXISTING_USER",
        permission="USE:AS_REFERENCE")
    entity.update(flags={"ACL": None})
    assert entity.messages["Warning", 1104][0] == "User Role does not exist."

    entity.deny(
        role="ALSO_NON_EXISTING_ROLE",
        permission="USE:AS_REFERENCE")
    entity.update(flags={"ACL": None})
    assert entity.messages["Warning", 1104][0] == "User Role does not exist."


def test_issue_134():
    """multiple white space characters after `FROM`"""
    db.execute_query("SELECT pname FROM  ename")


def test_issue_131():
    """white space befor unit with strange character"""
    rt = db.RecordType(name="TestType").insert()
    prop = db.Property(name="TestProp", datatype=db.INTEGER, unit="€").insert()
    rec = db.Record(name="TestRecord").add_property(
        name=prop.name, value=101, unit="€")
    rec.add_parent(rt)
    rec.insert()
    result_ids = [ent.id for ent in db.execute_query(
        "FIND Entity WITH {} > 100 €".format(prop.name))]

    assert rec.id in result_ids

    result_ids = [ent.id for ent in db.execute_query(
        "FIND Entity WITH {} > 100.5 €".format(prop.name))]

    assert rec.id in result_ids


def test_issue_154_no_versioning():
    """ FIND MusicalInstrument WITH Manufacturer = "Antonio Stradivari" and
    FIND MusicalInstrument WITH Manufacturer != "Antonio Stradivari" """
    rt_man = db.RecordType("Manufacturer")
    rt_inst = db.RecordType("MusicalInstrument").add_property(rt_man)

    rec_man = db.Record("Antonio Stradivari").add_parent("Manufacturer")
    rec_man2 = db.Record("The other guy").add_parent("Manufacturer")
    rec_inst = db.Record("Violin").add_parent(
        "MusicalInstrument").add_property("Manufacturer", rec_man)
    rec_inst2 = db.Record("Guitar").add_parent(
        "MusicalInstrument").add_property("Manufacturer", rec_man2)
    rec_inst3 = db.Record("Broken Record").add_parent("MusicalInstrument")

    c = db.Container().extend([rt_man, rt_inst, rec_man, rec_inst, rec_man2,
                               rec_inst2, rec_inst3]).insert()

    assert "Violin" not in [e.name for e in db.execute_query(
        "FIND RECORD MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")]
    assert "Violin" not in [e.name for e in db.execute_query(
        "FIND RECORD MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")]
    assert len(db.execute_query("FIND MusicalInstrument")) == 4
    assert len(db.execute_query("FIND RECORD MusicalInstrument")) == 3
    assert len(db.execute_query(
        "FIND MusicalInstrument WITH Manufacturer")) == 3
    assert len(db.execute_query(
        "FIND RECORD MusicalInstrument WITH Manufacturer")) == 2
    assert rec_inst.id == db.execute_query(
        "FIND MusicalInstrument WITH Manufacturer = 'Antonio Stradivari'",
        unique=True).id
    assert len(db.execute_query(
        "FIND MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")) == 3
    assert len(db.execute_query(
        "FIND RECORD MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")) == 2
    assert len(db.execute_query(
        "FIND MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")) == 1
    assert len(db.execute_query(
        "FIND RECORD MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")) == 1


def test_issue_154_with_versioning():
    """ FIND MusicalInstrument WITH Manufacturer = "Antonio Stradivari" and
    FIND MusicalInstrument WITH Manufacturer != "Antonio Stradivari" """
    rt_man = db.RecordType("Manufacturer")
    rt_inst = db.RecordType("MusicalInstrument").add_property(rt_man)

    rec_man = db.Record("Antonio Stradivari").add_parent("Manufacturer")
    rec_man2 = db.Record("The other guy").add_parent("Manufacturer")
    rec_inst = db.Record("Violin").add_parent(
        "MusicalInstrument").add_property("Manufacturer", rec_man)
    rec_inst2 = db.Record("Guitar").add_parent(
        "MusicalInstrument").add_property("Manufacturer", rec_man2)
    rec_inst3 = db.Record("Broken Record").add_parent("MusicalInstrument")

    db.Container().extend([rt_man, rt_inst, rec_man,
                           rec_inst, rec_man2, rec_inst2, rec_inst3]).insert()

    assert "Violin" not in [e.name for e in db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")]
    assert "Violin" not in [e.name for e in db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")]
    assert len(db.execute_query("FIND ANY VERSION OF MusicalInstrument")) == 4
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument")) == 3
    assert len(db.execute_query(
        "FIND ANY VERSION OF MusicalInstrument WITH Manufacturer")) == 3
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH Manufacturer")) == 2
    assert rec_inst.id == db.execute_query(
        "FIND ANY VERSION OF MusicalInstrument WITH Manufacturer = 'Antonio Stradivari'",
        unique=True).id
    assert len(db.execute_query(
        "FIND ANY VERSION OF MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")) == 3
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")) == 2
    assert len(db.execute_query(
        "FIND ANY VERSION OF MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")) == 1
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")) == 1

    # now, some updates
    rt_man.description = "Updated Description"
    rt_inst.description = "Updated Description"

    rec_man.description = "Updated Description"
    rec_man2.description = "Updated Description"
    rec_inst.description = "Updated Description"
    rec_inst2.description = "Updated Description"
    rec_inst3.description = "Updated Description"
    db.Container().extend([rt_man, rt_inst, rec_man,
                           rec_inst, rec_man2, rec_inst2, rec_inst3]).update()

    assert "Violin" not in [e.name for e in db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")]
    assert "Violin" not in [e.name for e in db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")]
    assert len(db.execute_query("FIND ANY VERSION OF MusicalInstrument")) == 8
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument")) == 6
    assert len(db.execute_query(
        "FIND ANY VERSION OF MusicalInstrument WITH Manufacturer")) == 6
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH Manufacturer")) == 4
    assert len(db.execute_query(
        "FIND ANY VERSION OF MusicalInstrument WITH Manufacturer = 'Antonio Stradivari'")) == 2
    assert len(db.execute_query(
        "FIND ANY VERSION OF MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")) == 6
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH NOT Manufacturer = 'Antonio Stradivari'")) == 4
    assert len(db.execute_query(
        "FIND ANY VERSION OF MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")) == 2
    assert len(db.execute_query(
        "FIND ANY VERSION OF RECORD MusicalInstrument WITH Manufacturer != 'Antonio Stradivari'")) == 2


def test_issue_127():
    """https://gitlab.com/caosdb/caosdb-server/-/issues/127"""
    p = db.Property("TestProp", datatype=db.LIST(db.DOUBLE)).insert()
    rt = db.RecordType("TestRT").add_property("TestProp", ["NaN"]).insert()