# Copyright 2013 - Mirantis, Inc.
# Copyright 2018 - Extreme Networks, Inc.
#
#    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.

import wsme
from wsme import types as wtypes

from mistral.api.controllers import resource
from mistral.api.controllers.v2 import types
from mistral import exceptions as exc
from mistral import utils
from mistral.workflow import states

SCOPE_TYPES = wtypes.Enum(str, 'private', 'public')


class ScopedResource(object):
    """Utilities for scoped resources"""
    @classmethod
    def validate_scope(cls, scope):
        if scope not in SCOPE_TYPES.values:
            raise exc.InvalidModelException(
                "Scope must be one of the following: %s; actual: "
                "%s" % (SCOPE_TYPES.values, scope)
            )


class Workbook(resource.Resource, ScopedResource):
    """Workbook resource."""

    id = wtypes.text
    name = wtypes.text
    namespace = wtypes.text

    definition = wtypes.text
    "workbook definition in Mistral v2 DSL"
    tags = [wtypes.text]
    scope = SCOPE_TYPES
    "'private' or 'public'"
    project_id = wsme.wsattr(wtypes.text, readonly=True)

    created_at = wtypes.text
    updated_at = wtypes.text

    @classmethod
    def sample(cls):
        return cls(id='123e4567-e89b-12d3-a456-426655440000',
                   name='book',
                   definition='HERE GOES'
                        'WORKBOOK DEFINITION IN MISTRAL DSL v2',
                   tags=['large', 'expensive'],
                   scope='private',
                   project_id='a7eb669e9819420ea4bd1453e672c0a7',
                   created_at='1970-01-01T00:00:00.000000',
                   updated_at='1970-01-01T00:00:00.000000',
                   namespace='')


class Workbooks(resource.ResourceList):
    """A collection of Workbooks."""

    workbooks = [Workbook]

    def __init__(self, **kwargs):
        self._type = 'workbooks'

        super(Workbooks, self).__init__(**kwargs)

    @classmethod
    def sample(cls):
        return cls(workbooks=[Workbook.sample()])


class Workflow(resource.Resource, ScopedResource):
    """Workflow resource."""

    id = wtypes.text
    name = wtypes.text
    namespace = wtypes.text
    input = wtypes.text

    definition = wtypes.text
    "Workflow definition in Mistral v2 DSL"
    tags = [wtypes.text]
    scope = SCOPE_TYPES
    "'private' or 'public'"
    project_id = wtypes.text

    created_at = wtypes.text
    updated_at = wtypes.text

    @classmethod
    def sample(cls):
        return cls(id='123e4567-e89b-12d3-a456-426655440000',
                   name='flow',
                   input='param1, param2',
                   definition='HERE GOES'
                        'WORKFLOW DEFINITION IN MISTRAL DSL v2',
                   tags=['large', 'expensive'],
                   scope='private',
                   project_id='a7eb669e9819420ea4bd1453e672c0a7',
                   created_at='1970-01-01T00:00:00.000000',
                   updated_at='1970-01-01T00:00:00.000000',
                   namespace='')

    @classmethod
    def _set_input(cls, obj, wf_spec):
        input_list = []

        if wf_spec:
            input = wf_spec.get('input', [])

            for param in input:
                if isinstance(param, dict):
                    for k, v in param.items():
                        input_list.append("%s=%s" % (k, v))
                else:
                    input_list.append(param)

            setattr(obj, 'input', ", ".join(input_list) if input_list else '')

        return obj

    @classmethod
    def from_dict(cls, d):
        obj = super(Workflow, cls).from_dict(d)

        return cls._set_input(obj, d.get('spec'))

    @classmethod
    def from_db_model(cls, db_model):
        obj = super(Workflow, cls).from_db_model(db_model)

        return cls._set_input(obj, db_model.spec)

    @classmethod
    def from_tuples(cls, tuple_iterator):
        obj = cls()
        spec = None
        for col_name, col_val in tuple_iterator:
            if hasattr(obj, col_name):
                # Convert all datetime values to strings.
                setattr(obj, col_name, utils.datetime_to_str(col_val))
            if col_name == 'spec':
                spec = col_val

        if spec:
            obj = cls._set_input(obj, spec)

        return obj


class Workflows(resource.ResourceList):
    """A collection of workflows."""

    workflows = [Workflow]

    def __init__(self, **kwargs):
        self._type = 'workflows'

        super(Workflows, self).__init__(**kwargs)

    @classmethod
    def sample(cls):
        workflows_sample = cls()
        workflows_sample.workflows = [Workflow.sample()]
        workflows_sample.next = ("http://localhost:8989/v2/workflows?"
                                 "sort_keys=id,name&"
                                 "sort_dirs=asc,desc&limit=10&"
                                 "marker=123e4567-e89b-12d3-a456-426655440000")

        return workflows_sample


class Action(resource.Resource, ScopedResource):
    """Action resource.

    NOTE: *name* is immutable. Note that name and description get inferred
    from action definition when Mistral service receives a POST request.
    So they can't be changed in another way.

    """

    id = wtypes.text
    name = wtypes.text
    is_system = bool
    input = wtypes.text

    description = wtypes.text
    tags = [wtypes.text]
    definition = wtypes.text
    scope = SCOPE_TYPES
    project_id = wsme.wsattr(wtypes.text, readonly=True)

    created_at = wtypes.text
    updated_at = wtypes.text

    @classmethod
    def sample(cls):
        return cls(
            id='123e4567-e89b-12d3-a456-426655440000',
            name='flow',
            definition='HERE GOES ACTION DEFINITION IN MISTRAL DSL v2',
            tags=['large', 'expensive'],
            scope='private',
            project_id='a7eb669e9819420ea4bd1453e672c0a7',
            created_at='1970-01-01T00:00:00.000000',
            updated_at='1970-01-01T00:00:00.000000'
        )


class Actions(resource.ResourceList):
    """A collection of Actions."""

    actions = [Action]

    def __init__(self, **kwargs):
        self._type = 'actions'

        super(Actions, self).__init__(**kwargs)

    @classmethod
    def sample(cls):
        sample = cls()
        sample.actions = [Action.sample()]
        sample.next = (
            "http://localhost:8989/v2/actions?sort_keys=id,name&"
            "sort_dirs=asc,desc&limit=10&"
            "marker=123e4567-e89b-12d3-a456-426655440000"
        )

        return sample


class Execution(resource.Resource):
    """Execution resource."""

    id = wtypes.text
    "execution ID. It is immutable and auto assigned or determined by the API "
    "client on execution creation. "
    "If it's passed to POST method from a client it'll be assigned to the "
    "newly created execution object, but only if an execution with such ID "
    "doesn't exist. If it exists, then the endpoint will just return "
    "execution properties in JSON."

    workflow_id = wtypes.text
    "workflow ID"

    workflow_name = wtypes.text
    "workflow name"

    workflow_namespace = wtypes.text
    """Workflow namespace. The workflow namespace is also saved
     under params and passed to all sub-workflow executions. When looking for
     the next sub-workflow to run, The correct workflow will be found by
     name and namespace, where the namespace can be the workflow namespace or
     the default namespace. Workflows in the same namespace as the top workflow
     will be given a higher priority."""

    description = wtypes.text
    "description of workflow execution"

    params = types.jsontype
    """'params' define workflow type specific parameters. Specific parameters
     are:
     'task_name' - the name of the target task. Only for reverse workflows.
     'env' - A string value containing the name of the stored environment
     object or a dictionary with the environment variables used during
     workflow execution and accessible as 'env()' from within expressions
     (YAQL or Jinja) defined in the workflow text.
     'evaluate_env' - If present, controls whether or not Mistral should
     recursively find and evaluate all expressions (YAQL or Jinja) within
     the specified environment (via 'env' parameter). 'True' - evaluate
     all expressions recursively in the environment structure. 'False' -
     don't evaluate expressions. 'True' by default.
    """

    task_execution_id = wtypes.text
    "reference to the parent task execution"

    root_execution_id = wtypes.text
    "reference to the root execution"

    source_execution_id = wtypes.text
    """reference to a workflow execution id which will signal the api to
    perform a lookup of a current workflow_execution and create a replica
    based on that workflow inputs and parameters"""

    state = wtypes.text
    "state can be one of: IDLE, RUNNING, SUCCESS, ERROR, PAUSED"

    state_info = wtypes.text
    "an optional state information string"

    input = types.jsontype
    "input is a JSON structure containing workflow input values"

    output = types.jsontype
    "output is a workflow output"

    created_at = wtypes.text
    updated_at = wtypes.text

    project_id = wsme.wsattr(wtypes.text, readonly=True)

    @classmethod
    def sample(cls):
        return cls(
            id='123e4567-e89b-12d3-a456-426655440000',
            workflow_name='flow',
            workflow_namespace='some_namespace',
            workflow_id='123e4567-e89b-12d3-a456-426655441111',
            description='this is the first execution.',
            project_id='40a908dbddfe48ad80a87fb30fa70a03',
            state='SUCCESS',
            input={},
            output={},
            params={
                'env': {'k1': 'abc', 'k2': 123},
                'notify': [
                    {
                        'type': 'webhook',
                        'url': 'http://endpoint/of/webhook',
                        'headers': {
                            'Content-Type': 'application/json',
                            'X-Auth-Token': '123456789'
                        }
                    },
                    {
                        'type': 'queue',
                        'topic': 'failover_queue',
                        'backend': 'rabbitmq',
                        'host': '127.0.0.1',
                        'port': 5432
                    }
                ]
            },
            created_at='1970-01-01T00:00:00.000000',
            updated_at='1970-01-01T00:00:00.000000'
        )


class Executions(resource.ResourceList):
    """A collection of Execution resources."""

    executions = [Execution]

    def __init__(self, **kwargs):
        self._type = 'executions'

        super(Executions, self).__init__(**kwargs)

    @classmethod
    def sample(cls):
        sample = cls()
        sample.executions = [Execution.sample()]
        sample.next = (
            "http://localhost:8989/v2/executions?"
            "sort_keys=id,workflow_name&sort_dirs=asc,desc&limit=10&"
            "marker=123e4567-e89b-12d3-a456-426655440000"
        )

        return sample


class Task(resource.Resource):
    """Task resource."""

    id = wtypes.text
    name = wtypes.text
    type = wtypes.text

    workflow_name = wtypes.text
    workflow_namespace = wtypes.text
    workflow_id = wtypes.text
    workflow_execution_id = wtypes.text

    state = wtypes.text
    """state can take one of the following values:
     IDLE, RUNNING, SUCCESS, ERROR, DELAYED"""

    state_info = wtypes.text
    "an optional state information string"

    project_id = wsme.wsattr(wtypes.text, readonly=True)

    runtime_context = types.jsontype

    result = wtypes.text
    published = types.jsontype
    processed = bool

    created_at = wtypes.text
    updated_at = wtypes.text

    # Add this param to make Mistral API work with WSME 0.8.0 or higher version
    reset = wsme.wsattr(bool, mandatory=True)

    env = types.jsontype

    @classmethod
    def sample(cls):
        return cls(
            id='123e4567-e89b-12d3-a456-426655440000',
            workflow_name='flow',
            workflow_id='123e4567-e89b-12d3-a456-426655441111',
            workflow_execution_id='123e4567-e89b-12d3-a456-426655440000',
            name='task',
            state=states.SUCCESS,
            project_id='40a908dbddfe48ad80a87fb30fa70a03',
            runtime_context={
                'triggered_by': [
                    {
                        'task_id': '123-123-123',
                        'event': 'on-success'
                    }
                ]
            },
            result='task result',
            published={'key': 'value'},
            processed=True,
            created_at='1970-01-01T00:00:00.000000',
            updated_at='1970-01-01T00:00:00.000000',
            reset=True
        )


class Tasks(resource.ResourceList):
    """A collection of tasks."""

    tasks = [Task]

    def __init__(self, **kwargs):
        self._type = 'tasks'

        super(Tasks, self).__init__(**kwargs)

    @classmethod
    def sample(cls):
        return cls(tasks=[Task.sample()])


class ActionExecution(resource.Resource):
    """ActionExecution resource."""

    id = wtypes.text

    workflow_name = wtypes.text
    workflow_namespace = wtypes.text
    task_name = wtypes.text
    task_execution_id = wtypes.text

    state = wtypes.text

    state_info = wtypes.text
    tags = [wtypes.text]
    name = wtypes.text
    description = wtypes.text
    project_id = wsme.wsattr(wtypes.text, readonly=True)
    accepted = bool
    input = types.jsontype
    output = types.jsontype
    created_at = wtypes.text
    updated_at = wtypes.text
    params = types.jsontype  # TODO(rakhmerov): What is this??

    @classmethod
    def sample(cls):
        return cls(
            id='123e4567-e89b-12d3-a456-426655440000',
            workflow_name='flow',
            task_name='task1',
            workflow_execution_id='653e4127-e89b-12d3-a456-426655440076',
            task_execution_id='343e45623-e89b-12d3-a456-426655440090',
            state=states.SUCCESS,
            state_info=states.SUCCESS,
            tags=['foo', 'fee'],
            name='std.echo',
            description='My running action',
            project_id='40a908dbddfe48ad80a87fb30fa70a03',
            accepted=True,
            input={'first_name': 'John', 'last_name': 'Doe'},
            output={'some_output': 'Hello, John Doe!'},
            created_at='1970-01-01T00:00:00.000000',
            updated_at='1970-01-01T00:00:00.000000',
            params={'save_result': True, "run_sync": False}
        )


class ActionExecutions(resource.ResourceList):
    """A collection of action_executions."""

    action_executions = [ActionExecution]

    def __init__(self, **kwargs):
        self._type = 'action_executions'

        super(ActionExecutions, self).__init__(**kwargs)

    @classmethod
    def sample(cls):
        return cls(action_executions=[ActionExecution.sample()])


class CronTrigger(resource.Resource):
    """CronTrigger resource."""

    id = wtypes.text
    name = wtypes.text
    workflow_name = wtypes.text
    workflow_id = wtypes.text
    workflow_input = types.jsontype
    workflow_params = types.jsontype
    project_id = wsme.wsattr(wtypes.text, readonly=True)

    scope = SCOPE_TYPES

    pattern = wtypes.text
    remaining_executions = wtypes.IntegerType(minimum=1)
    first_execution_time = wtypes.text
    next_execution_time = wtypes.text

    created_at = wtypes.text
    updated_at = wtypes.text

    @classmethod
    def sample(cls):
        return cls(
            id='123e4567-e89b-12d3-a456-426655440000',
            name='my_trigger',
            workflow_name='my_wf',
            workflow_id='123e4567-e89b-12d3-a456-426655441111',
            workflow_input={},
            workflow_params={},
            project_id='40a908dbddfe48ad80a87fb30fa70a03',
            scope='private',
            pattern='* * * * *',
            remaining_executions=42,
            created_at='1970-01-01T00:00:00.000000',
            updated_at='1970-01-01T00:00:00.000000'
        )


class CronTriggers(resource.ResourceList):
    """A collection of cron triggers."""

    cron_triggers = [CronTrigger]

    def __init__(self, **kwargs):
        self._type = 'cron_triggers'

        super(CronTriggers, self).__init__(**kwargs)

    @classmethod
    def sample(cls):
        return cls(cron_triggers=[CronTrigger.sample()])


class Environment(resource.Resource):
    """Environment resource."""

    id = wtypes.text
    name = wtypes.text
    description = wtypes.text
    variables = types.jsontype
    scope = SCOPE_TYPES
    project_id = wsme.wsattr(wtypes.text, readonly=True)
    created_at = wtypes.text
    updated_at = wtypes.text

    @classmethod
    def sample(cls):
        return cls(
            id='123e4567-e89b-12d3-a456-426655440000',
            name='sample',
            description='example environment entry',
            variables={
                'server': 'localhost',
                'database': 'temp',
                'timeout': 600,
                'verbose': True
            },
            scope='private',
            project_id='40a908dbddfe48ad80a87fb30fa70a03',
            created_at='1970-01-01T00:00:00.000000',
            updated_at='1970-01-01T00:00:00.000000'
        )


class Environments(resource.ResourceList):
    """A collection of Environment resources."""

    environments = [Environment]

    def __init__(self, **kwargs):
        self._type = 'environments'

        super(Environments, self).__init__(**kwargs)

    @classmethod
    def sample(cls):
        return cls(environments=[Environment.sample()])


class Member(resource.Resource):
    id = types.uuid
    resource_id = wtypes.text
    resource_type = wtypes.text
    project_id = wtypes.text
    member_id = wtypes.text
    status = wtypes.Enum(str, 'pending', 'accepted', 'rejected')
    created_at = wtypes.text
    updated_at = wtypes.text

    @classmethod
    def sample(cls):
        return cls(
            id='123e4567-e89b-12d3-a456-426655440000',
            resource_id='123e4567-e89b-12d3-a456-426655440011',
            resource_type='workflow',
            project_id='40a908dbddfe48ad80a87fb30fa70a03',
            member_id='a7eb669e9819420ea4bd1453e672c0a7',
            status='accepted',
            created_at='1970-01-01T00:00:00.000000',
            updated_at='1970-01-01T00:00:00.000000'
        )


class Members(resource.ResourceList):
    members = [Member]

    @classmethod
    def sample(cls):
        return cls(members=[Member.sample()])


class Service(resource.Resource):
    """Service resource."""

    name = wtypes.text

    type = wtypes.text

    @classmethod
    def sample(cls):
        return cls(name='host1_1234', type='executor_group')


class Services(resource.Resource):
    """A collection of Services."""

    services = [Service]

    @classmethod
    def sample(cls):
        return cls(services=[Service.sample()])


class EventTrigger(resource.Resource):
    """EventTrigger resource."""

    id = wsme.wsattr(wtypes.text, readonly=True)
    created_at = wsme.wsattr(wtypes.text, readonly=True)
    updated_at = wsme.wsattr(wtypes.text, readonly=True)
    project_id = wsme.wsattr(wtypes.text, readonly=True)
    name = wtypes.text
    workflow_id = types.uuid
    workflow_input = types.jsontype
    workflow_params = types.jsontype
    exchange = wtypes.text
    topic = wtypes.text
    event = wtypes.text
    scope = SCOPE_TYPES

    @classmethod
    def sample(cls):
        return cls(id='123e4567-e89b-12d3-a456-426655441414',
                   created_at='1970-01-01T00:00:00.000000',
                   updated_at='1970-01-01T00:00:00.000000',
                   project_id='project',
                   name='expiration_event_trigger',
                   workflow_id='123e4567-e89b-12d3-a456-426655441414',
                   workflow_input={},
                   workflow_params={},
                   exchange='nova',
                   topic='notifications',
                   event='compute.instance.create.end')


class EventTriggers(resource.ResourceList):
    """A collection of event triggers."""

    event_triggers = [EventTrigger]

    def __init__(self, **kwargs):
        self._type = 'event_triggers'

        super(EventTriggers, self).__init__(**kwargs)

    @classmethod
    def sample(cls):
        triggers_sample = cls()
        triggers_sample.event_triggers = [EventTrigger.sample()]
        triggers_sample.next = ("http://localhost:8989/v2/event_triggers?"
                                "sort_keys=id,name&"
                                "sort_dirs=asc,desc&limit=10&"
                                "marker=123e4567-e89b-12d3-a456-426655440000")

        return triggers_sample
