# copyright 2016-2022 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr -- mailto:contact@logilab.fr
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""cubicweb-dataprocessing schema"""

from yams.buildobjs import ComputedRelation, EntityType, RelationDefinition, Int, String

from cubicweb import _
from cubicweb.schema import (
    RRQLExpression,
    ERQLExpression,
    RQLConstraint,
    WorkflowableEntityType,
)

from cubicweb_file.schema import File


DATAPROCESS_UPDATE_PERMS_RQLEXPR = (
    'U in_group G, G name "users", '
    'X in_state S, S name "wfs_dataprocess_initialized"'
)


class _DataProcess(WorkflowableEntityType):
    __abstract__ = True
    __permissions__ = {
        "read": ("managers", "users", "guests"),
        "update": ("managers", ERQLExpression(DATAPROCESS_UPDATE_PERMS_RQLEXPR)),
        "delete": ("managers", ERQLExpression(DATAPROCESS_UPDATE_PERMS_RQLEXPR)),
        "add": ("managers", "users"),
    }


class DataTransformationProcess(_DataProcess):
    """Data transformation process"""


class DataValidationProcess(_DataProcess):
    """Data validation process"""


class process_depends_on(RelationDefinition):
    subject = "DataTransformationProcess"
    object = "DataValidationProcess"
    cardinality = "??"


class process_input_file(RelationDefinition):
    __permissions__ = {
        "read": ("managers", "users", "guests"),
        "add": (
            "managers",
            RRQLExpression(
                'U in_group G, G name "users", '
                'S in_state ST, ST name "wfs_dataprocess_initialized"'
            ),
        ),
        "delete": ("managers", RRQLExpression("U has_update_permission S")),
    }
    subject = ("DataTransformationProcess", "DataValidationProcess")
    object = "File"
    cardinality = "?*"
    description = _("input file of the data process")
    constraints = [
        RQLConstraint(
            "NOT EXISTS(SC implemented_by O)", msg=_("file is used by a script")
        ),
    ]


class validated_by(RelationDefinition):
    """A File may be validated by a validation process"""

    __permissions__ = {"read": ("managers", "users", "guests"), "add": (), "delete": ()}
    subject = "File"
    object = "DataValidationProcess"
    cardinality = "**"


class produced_by(RelationDefinition):
    """A File may be produced by a transformation process"""

    __permissions__ = {"read": ("managers", "users", "guests"), "add": (), "delete": ()}
    subject = "File"
    object = "DataTransformationProcess"
    cardinality = "?*"


class process_stderr(RelationDefinition):
    __permissions__ = {
        "read": (
            "managers",
            "users",
            "guests",
        ),
        "add": (),
        "delete": (),
    }
    subject = ("DataTransformationProcess", "DataValidationProcess")
    object = "File"
    cardinality = "??"
    inlined = True
    composite = "subject"
    description = _("standard error output")


# Set File permissions:
#  * use `produced_by` relation to prevent modification of generated files
#  * bind the update permissions on the Script which uses the File as
#    implementation if any
_update_file_perms = (
    "managers",
    ERQLExpression(
        'U in_group G, G name "users", '
        "NOT EXISTS(X produced_by Y), "
        "NOT EXISTS(S1 implemented_by X)"
        " OR EXISTS(S implemented_by X, U has_update_permission S)"
    ),
)
File.__permissions__ = File.__permissions__.copy()
File.__permissions__.update(
    {"update": _update_file_perms, "delete": _update_file_perms}
)


class _Script(EntityType):
    name = String(required=True, fulltextindexed=True)


class implemented_by(RelationDefinition):
    __permissions__ = {
        "read": ("managers", "users", "guests"),
        "add": (RRQLExpression("U has_update_permission S"),),
        "delete": (RRQLExpression("U has_update_permission S"),),
    }
    subject = ("ValidationScript", "TransformationScript")
    object = "File"
    cardinality = "1?"
    inlined = True
    composite = "subject"
    description = _("the resource (file) implementing a script")


_validationscript_update_perms = (
    "managers",
    ERQLExpression(
        'U in_group G, G name "users", '
        "NOT EXISTS(P1 validation_script X) OR "
        "EXISTS(P2 validation_script X, P2 in_state S,"
        '       S name "wfs_dataprocess_initialized")'
    ),
)


class ValidationScript(_Script):
    __permissions__ = {
        "read": ("managers", "users", "guests"),
        "update": _validationscript_update_perms,
        "delete": _validationscript_update_perms,
        "add": ("managers", "users"),
    }
    parameters = String(
        description=_(
            "parameters for the script, will be used as JSON data " "structure"
        )
    )


class validation_script(RelationDefinition):
    __permissions__ = {
        "read": ("managers", "users", "guests"),
        "add": ("managers", "users"),
        "delete": (RRQLExpression("U has_update_permission S"),),
    }
    subject = "DataValidationProcess"
    object = "ValidationScript"
    cardinality = "1*"
    inlined = True


_transformationscript_update_perms = (
    "managers",
    ERQLExpression(
        'U in_group G, G name "users", '
        "NOT EXISTS(S1 step_script X, S1 in_sequence SQ1,"
        "           P1 transformation_sequence SQ1) OR "
        "EXISTS(S2 step_script X, S2 in_sequence SQ2,"
        "       P2 transformation_sequence SQ2, P2 in_state S,"
        '       S name "wfs_dataprocess_initialized")'
    ),
)


class TransformationScript(_Script):
    __permissions__ = {
        "read": ("managers", "users", "guests"),
        "update": _transformationscript_update_perms,
        "delete": _transformationscript_update_perms,
        "add": ("managers", "users"),
    }
    output_format = String(
        maxsize=128, description=_("format (MIME type) of stderr data")
    )


class transformation_sequence(RelationDefinition):
    __permissions__ = {
        "read": ("managers", "users"),
        "add": (RRQLExpression("U has_update_permission S"),),
        "delete": (RRQLExpression("U has_update_permission S"),),
    }
    subject = "DataTransformationProcess"
    object = "TransformationSequence"
    cardinality = "1*"


_transformationsequence_update_perms = (
    ERQLExpression(
        'U in_group G, G name IN ("managers", "users"), '
        "NOT EXISTS(P transformation_sequence X) OR "
        "EXISTS(P transformation_sequence X,"
        '       P name "wfs_dataprocess_initialized")'
    ),
)


class TransformationSequence(EntityType):
    """A sequence of scripts involved in a given process."""

    __permissions__ = {
        "read": ("managers", "users", "guests"),
        "update": _transformationsequence_update_perms,
        "delete": _transformationsequence_update_perms,
        "add": ("managers", "users"),
    }


class TransformationStep(EntityType):
    """A step in a transformation sequence."""

    # TODO __permissions__
    index = Int(required=True, description=_("position in the transformation sequence"))
    parameters = String(
        description=_(
            "parameters for the script, will be used as JSON data " "structure"
        )
    )
    __unique_together__ = [("index", "in_sequence"), ("in_sequence", "step_script")]


class step_script(RelationDefinition):
    # TODO __permissions__
    subject = "TransformationStep"
    object = "TransformationScript"
    cardinality = "1*"
    inlined = True


class in_sequence(RelationDefinition):
    # TODO __permissions__
    subject = "TransformationStep"
    object = "TransformationSequence"
    cardinality = "1+"
    composite = "object"
    inlined = True


class transformation_scripts(ComputedRelation):
    rule = "S transformation_sequence SEQ, O in_sequence SEQ"
