#
# Copyright (c) 2020, Neptune Labs Sp. z o.o.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# pylint: disable=protected-access

import uuid

from neptune.new.exceptions import MetadataInconsistency
from neptune.new.internal.backends.operations_preprocessor import OperationsPreprocessor
from neptune.new.internal.operation import (
    AddStrings,
    AssignFloat,
    AssignString,
    ClearFloatLog,
    ClearImageLog,
    ClearStringSet,
    ConfigFloatSeries,
    DeleteAttribute,
    LogFloats,
    LogImages,
    LogStrings,
    RemoveStrings,
    TrackFilesToArtifact,
    UploadFileSet
)

from tests.neptune.new.attributes.test_attribute_base import TestAttributeBase

FLog = LogFloats.ValueType
SLog = LogStrings.ValueType
ILog = LogImages.ValueType


class TestOperationsPreprocessor(TestAttributeBase):

    def test_delete_attribute(self):
        # given
        processor = OperationsPreprocessor()

        # when
        processor.process([
            DeleteAttribute(["a"]),
            AssignFloat(["a"], 1),
            DeleteAttribute(["a"]),

            AssignString(["b"], "2"),
            DeleteAttribute(["b"]),

            AssignString(["c"], "2"),
            AssignString(["c"], "3"),
            DeleteAttribute(["c"]),

            DeleteAttribute(["d"]),
            DeleteAttribute(["d"]),

            DeleteAttribute(["e"]),
        ])

        # then
        self.assertEqual(processor.get_operations(), [
            DeleteAttribute(["a"]),

            AssignString(["b"], "2"),
            DeleteAttribute(["b"]),

            AssignString(["c"], "3"),
            DeleteAttribute(["c"]),

            DeleteAttribute(["d"]),

            DeleteAttribute(["e"]),
        ])
        self.assertEqual(processor.get_errors(), [])

    def test_assign(self):
        # given
        processor = OperationsPreprocessor()

        # when
        processor.process([
            AssignFloat(["a"], 1),
            DeleteAttribute(["a"]),
            AssignString(["a"], "111"),

            DeleteAttribute(["b"]),
            AssignFloat(["b"], 2),

            AssignFloat(["c"], 3),
            AssignString(["c"], "333"),

            AssignString(["d"], "44"),

            AssignFloat(["e"], 5),
            AssignFloat(["e"], 10),
            AssignFloat(["e"], 33),
        ])

        # then
        self.assertEqual(processor.get_operations(), [
            AssignFloat(["a"], 1),
            DeleteAttribute(["a"]),
            AssignString(["a"], "111"),

            DeleteAttribute(["b"]),
            AssignFloat(["b"], 2),

            AssignFloat(["c"], 3),

            AssignString(["d"], "44"),

            AssignFloat(["e"], 33),
        ])
        self.assertEqual(processor.get_errors(), [
            MetadataInconsistency("Cannot perform AssignString operation on c: Attribute is not a String")
        ])

    def test_series(self):
        # given
        processor = OperationsPreprocessor()

        # when
        processor.process([
            LogFloats(["a"], [FLog(1, 2, 3)]),
            ConfigFloatSeries(["a"], min=7, max=70, unit="%"),
            DeleteAttribute(["a"]),
            LogStrings(["a"], [SLog("111", 3, 4)]),

            DeleteAttribute(["b"]),
            LogStrings(["b"], [SLog("222", None, 6)]),

            LogFloats(["c"], [FLog(1, 2, 3)]),
            LogFloats(["c"], [FLog(10, 20, 30), FLog(100, 200, 300)]),

            LogStrings(["d"], [SLog("4", 111, 222)]),

            ClearFloatLog(["e"]),

            LogImages(["f"], [ILog("1", 2, 3)]),
            LogImages(["f"], [ILog("10", 20, 30), FLog("100", 200, 300)]),
            LogImages(["f"], [ILog("1", 2, 3)]),
            LogImages(["f"], [ILog("10", 20, 30), FLog("100", 200, 300)]),
            ClearImageLog(["f"]),
            LogImages(["f"], [ILog("3", 20, 30), FLog("4", 200, 300)]),
            LogImages(["f"], [ILog("5", 2, 3)]),
            LogImages(["f"], [ILog("8", 20, 30), FLog("1000", 200, 300)]),

            LogImages(["g"], [ILog("10", 20, 30), FLog("100", 200, 300)]),
            ClearImageLog(["g"]),

            AssignString(["h"], "44"),
            LogFloats(["h"], [FLog(10, 20, 30), FLog(100, 200, 300)]),

            LogFloats(["i"], [FLog(1, 2, 3)]),
            ConfigFloatSeries(["i"], min=7, max=70, unit="%"),
            ClearFloatLog(["i"]),
            LogFloats(["i"], [FLog(10, 20, 30), FLog(100, 200, 300)]),
        ])

        # then
        self.assertEqual(processor.get_operations(), [
            LogFloats(["a"], [FLog(1, 2, 3)]),
            DeleteAttribute(["a"]),
            LogStrings(["a"], [FLog("111", 3, 4)]),

            DeleteAttribute(["b"]),
            LogStrings(["b"], [SLog("222", None, 6)]),

            LogFloats(["c"], [FLog(1, 2, 3), FLog(10, 20, 30), FLog(100, 200, 300)]),

            LogStrings(["d"], [SLog("4", 111, 222)]),

            ClearFloatLog(["e"]),

            ClearImageLog(["f"]),
            LogImages(["f"], [ILog("3", 20, 30), FLog("4", 200, 300), ILog("5", 2, 3),
                              ILog("8", 20, 30), FLog("1000", 200, 300)]),

            ClearImageLog(["g"]),

            AssignString(["h"], "44"),

            ClearFloatLog(["i"]),
            LogFloats(["i"], [FLog(10, 20, 30), FLog(100, 200, 300)]),
            ConfigFloatSeries(["i"], min=7, max=70, unit="%"),
        ])
        self.assertEqual(processor.get_errors(), [
            MetadataInconsistency("Cannot perform LogFloats operation on h: Attribute is not a Float Series")
        ])

    def test_sets(self):
        # given
        processor = OperationsPreprocessor()

        # when
        processor.process([
            AddStrings(["a"], {"xx", "y", "abc"}),
            DeleteAttribute(["a"]),
            AddStrings(["a"], {"hhh", "gij"}),

            DeleteAttribute(["b"]),
            RemoveStrings(["b"], {"abc", "defgh"}),

            AddStrings(["c"], {"hhh", "gij"}),
            RemoveStrings(["c"], {"abc", "defgh"}),
            AddStrings(["c"], {"qqq"}),

            ClearStringSet(["d"]),

            RemoveStrings(["e"], {"abc", "defgh"}),
            AddStrings(["e"], {"qqq"}),
            ClearStringSet(["e"]),

            AddStrings(["f"], {"hhh", "gij"}),
            RemoveStrings(["f"], {"abc", "defgh"}),
            AddStrings(["f"], {"qqq"}),
            ClearStringSet(["f"]),
            AddStrings(["f"], {"xx", "y", "abc"}),
            RemoveStrings(["f"], {"abc", "defgh"}),

            AssignString(["h"], "44"),
            RemoveStrings(["h"], {""}),

            AssignFloat(["i"], 5),
            AddStrings(["i"], {""}),
        ])

        # then
        self.assertEqual(processor.get_operations(), [
            AddStrings(["a"], {"xx", "y", "abc"}),
            DeleteAttribute(["a"]),
            AddStrings(["a"], {"hhh", "gij"}),

            DeleteAttribute(["b"]),
            RemoveStrings(["b"], {"abc", "defgh"}),

            AddStrings(["c"], {"hhh", "gij"}),
            RemoveStrings(["c"], {"abc", "defgh"}),
            AddStrings(["c"], {"qqq"}),

            ClearStringSet(["d"]),

            ClearStringSet(["e"]),

            ClearStringSet(["f"]),
            AddStrings(["f"], {"xx", "y", "abc"}),
            RemoveStrings(["f"], {"abc", "defgh"}),

            AssignString(["h"], "44"),
            AssignFloat(["i"], 5),
        ])
        self.assertEqual(processor.get_errors(), [
            MetadataInconsistency("Cannot perform RemoveStrings operation on h: Attribute is not a String Set"),
            MetadataInconsistency("Cannot perform AddStrings operation on i: Attribute is not a String Set")
        ])

    def test_file_set(self):
        # given
        processor = OperationsPreprocessor()

        # when
        processor.process([
            UploadFileSet(["a"], ["xx", "y", "abc"], reset=False),
            DeleteAttribute(["a"]),
            UploadFileSet(["a"], ["hhh", "gij"], reset=False),

            DeleteAttribute(["b"]),
            UploadFileSet(["b"], ["abc", "defgh"], reset=True),

            UploadFileSet(["c"], ["hhh", "gij"], reset=False),
            UploadFileSet(["c"], ["abc", "defgh"], reset=True),
            UploadFileSet(["c"], ["qqq"], reset=False),

            UploadFileSet(["d"], ["hhh", "gij"], reset=False),

            AssignFloat(["e"], 5),
            UploadFileSet(["e"], [""], reset=False),
        ])

        # then
        self.assertEqual(processor.get_operations(), [
            UploadFileSet(["a"], ["xx", "y", "abc"], reset=False),
            DeleteAttribute(["a"]),
            UploadFileSet(["a"], ["hhh", "gij"], reset=False),

            DeleteAttribute(["b"]),
            UploadFileSet(["b"], ["abc", "defgh"], reset=True),

            UploadFileSet(["c"], ["abc", "defgh"], reset=True),
            UploadFileSet(["c"], ["qqq"], reset=False),

            UploadFileSet(["d"], ["hhh", "gij"], reset=False),

            AssignFloat(["e"], 5),
        ])
        self.assertEqual(processor.get_errors(), [
            MetadataInconsistency("Cannot perform UploadFileSet operation on e: Attribute is not a File Set")
        ])

    def test_artifacts(self):
        # given
        processor = OperationsPreprocessor()
        project_uuid = uuid.uuid4()

        # when
        processor.process([
            TrackFilesToArtifact(["a"], project_uuid, [('dir1/', None)]),
            DeleteAttribute(["a"]),

            TrackFilesToArtifact(["b"], project_uuid, [('dir1/', None)]),
            TrackFilesToArtifact(["b"], project_uuid, [('dir2/dir3/', 'dir2/')]),
            TrackFilesToArtifact(["b"], project_uuid, [('dir4/dir5/', 'dir4/')]),

            AssignFloat(["c"], 5),
            TrackFilesToArtifact(["c"], project_uuid, [('dir1/', None)]),

            TrackFilesToArtifact(["d"], project_uuid, [('dir2/dir3/', 'dir2/')]),
            TrackFilesToArtifact(["d"], project_uuid, [('dir4/', None)]),

            TrackFilesToArtifact(["e"], project_uuid, [('dir1/', None)]),
            TrackFilesToArtifact(["e"], project_uuid, [('dir2/dir3/', 'dir2/')]),

            TrackFilesToArtifact(["f"], project_uuid, [('dir1/', None)]),
            TrackFilesToArtifact(["f"], project_uuid, [('dir2/dir3/', 'dir2/')]),
            TrackFilesToArtifact(["f"], project_uuid, [('dir4/', None)]),
        ])

        # then
        self.assertEqual(processor.get_operations(), [
            TrackFilesToArtifact(["a"], project_uuid, [('dir1/', None)]),
            DeleteAttribute(["a"]),

            TrackFilesToArtifact(
                ["b"],
                project_uuid,
                [('dir1/', None), ('dir2/dir3/', 'dir2/'), ('dir4/dir5/', 'dir4/')]
            ),

            AssignFloat(["c"], 5),

            TrackFilesToArtifact(["d"], project_uuid, [('dir2/dir3/', 'dir2/'), ('dir4/', None)]),

            TrackFilesToArtifact(["e"], project_uuid, [('dir1/', None), ('dir2/dir3/', 'dir2/')]),

            TrackFilesToArtifact(
                ["f"],
                project_uuid,
                [('dir1/', None), ('dir2/dir3/', 'dir2/'), ('dir4/', None)]
            ),
        ])
        self.assertEqual(processor.get_errors(), [
            MetadataInconsistency(
                "Cannot perform TrackFilesToArtifact operation on c: Attribute is not a Artifact"
            ),
        ])
