# Copyright 2014 - StackStorm, 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 itertools

from django.conf import settings
from django.utils.translation import gettext_lazy as _

from horizon.utils import functions as utils
from horizon.utils import memoized

from mistralclient.api import client as mistral_client
from mistraldashboard.handle_errors import handle_errors
from openstack_dashboard.api import base

SERVICE_TYPE = 'workflowv2'


@memoized.memoized
def mistralclient(request):
    return mistral_client.client(
        username=request.user.username,
        auth_token=request.user.token.id,
        project_id=request.user.tenant_id,
        # We can't use auth_url param in here if we config
        # and use keystone federation
        mistral_url=base.url_for(request, 'workflowv2'),
        # Todo: add SECONDARY_ENDPOINT_TYPE support
        endpoint_type=getattr(
            settings,
            'OPENSTACK_ENDPOINT_TYPE',
            'internalURL'
        ),
        service_type=SERVICE_TYPE,
        # We should not treat definition as file path or uri otherwise
        # we allow access to contents in internal servers
        enforce_raw_definition=False
    )


@handle_errors(_("Unable to retrieve list"), [])
def pagination_list(entity, request, marker='', sort_keys='', sort_dirs='asc',
                    paginate=False, reversed_order=False, selector=None):
    """Retrieve a listing of specific entity and handles pagination.

    :param entity: Requested entity (String)
    :param request: Request data
    :param marker: Pagination marker for large data sets: entity id
    :param sort_keys: Columns to sort results by
    :param sort_dirs: Sorting Directions (asc/desc). Default:asc
    :param paginate: If true will perform pagination based on settings.
                     Default:False
    :param reversed_order: flag to reverse list. Default:False
    :param selector: additional selector to allow further server filtering
    """

    limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
    page_size = utils.get_page_size(request)

    if paginate:
        request_size = page_size + 1
    else:
        request_size = limit

    if reversed_order:
        sort_dirs = 'desc' if sort_dirs == 'asc' else 'asc'

    api = mistralclient(request)
    entities_iter = (
        getattr(api, entity).list(
            selector,
            marker=marker,
            limit=limit,
            sort_keys=sort_keys,
            sort_dirs=sort_dirs
        ) if selector else (
            getattr(api, entity).list(
                marker=marker,
                limit=limit,
                sort_keys=sort_keys,
                sort_dirs=sort_dirs
            )
        )
    )

    has_prev_data = has_more_data = False

    if paginate:
        entities = list(itertools.islice(entities_iter, request_size))
        # first and middle page condition
        if len(entities) > page_size:
            entities.pop(-1)
            has_more_data = True
            # middle page condition
            if marker is not None:
                has_prev_data = True
        # first page condition when reached via prev back
        elif reversed_order and marker is not None:
            has_more_data = True
        # last page condition
        elif marker is not None:
            has_prev_data = True

        # restore the original ordering here
        if reversed_order:
            entities = sorted(entities,
                              key=lambda ent:
                                  (getattr(ent, sort_keys) or '').lower(),
                              reverse=(sort_dirs == 'desc')
                              )
    else:
        entities = list(entities_iter)

    return entities, has_more_data, has_prev_data


def execution_create(request, **data):
    """Creates new execution."""

    return mistralclient(request).executions.create(**data)


def execution_get(request, execution_id):
    """Get specific execution.

    :param execution_id: Execution ID
    """

    return mistralclient(request).executions.get(execution_id)


def execution_update(request, execution_id, field, value):
    """update specific execution field, either state or description.

    :param request: Request data
    :param execution_id: Execution ID
    :param field: flag - either Execution state or description
    :param value: new update value
    """

    if field == "state":
        return mistralclient(request).\
            executions.update(execution_id, value)
    elif field == "description":
        return mistralclient(request).\
            executions.update(execution_id, None, value)


def execution_delete(request, execution_name):
    """Delete execution.

    :param execution_name: Execution name
    """

    return mistralclient(request).executions.delete(execution_name)


@handle_errors(_("Unable to retrieve tasks."), [])
def task_list(request, execution_id=None):
    """Returns all tasks.

    :param execution_id: Workflow execution ID associated with list of tasks
    """

    return mistralclient(request).tasks.list(execution_id)


def task_get(request, task_id=None):
    """Get specific task.

    :param task_id: Task ID
    """

    return mistralclient(request).tasks.get(task_id)


@handle_errors(_("Unable to retrieve workflows"), [])
def workflow_list(request):
    """Returns all workflows."""

    return mistralclient(request).workflows.list()


def workflow_get(request, workflow_name):
    """Get specific workflow.

    :param workflow_name: Workflow name
    """

    return mistralclient(request).workflows.get(workflow_name)


def workflow_create(request, workflows_definition):
    """Create workflow.

    :param workflows_definition: Workflows definition
    """

    return mistralclient(request).workflows.create(workflows_definition)


def workflow_validate(request, workflow_definition):
    """Validate workflow.

    :param workflow_definition: Workflow definition
    """

    return mistralclient(request).workflows.validate(workflow_definition)


def workflow_delete(request, workflow_name):
    """Delete workflow.

    :param workflow_name: Workflow name
    """

    return mistralclient(request).workflows.delete(workflow_name)


def workflow_update(request, workflows_definition):
    """Update workflow.

    :param workflows_definition: Workflows definition
    """

    return mistralclient(request).workflows.update(workflows_definition)


@handle_errors(_("Unable to retrieve workbooks."), [])
def workbook_list(request):
    """Returns all workbooks."""

    return mistralclient(request).workbooks.list()


def workbook_get(request, workbook_name):
    """Get specific workbook.

    :param workbook_name: Workbook name
    """

    return mistralclient(request).workbooks.get(workbook_name)


def workbook_create(request, workbook_definition):
    """Create workbook.

    :param workbook_definition: Workbook definition
    """

    return mistralclient(request).workbooks.create(workbook_definition)


def workbook_validate(request, workbook_definition):
    """Validate workbook.

    :param workbook_definition: Workbook definition
    """

    return mistralclient(request).workbooks.validate(workbook_definition)


def workbook_delete(request, workbook_name):
    """Delete workbook.

    :param workbook_name: Workbook name
    """

    return mistralclient(request).workbooks.delete(workbook_name)


def workbook_update(request, workbook_definition):
    """Update workbook.

    :param workbook_definition: Workbook definition
    """

    return mistralclient(request).workbooks.update(workbook_definition)


@handle_errors(_("Unable to retrieve actions."), [])
def action_list(request):
    """Returns all actions."""

    return mistralclient(request).actions.list()


def action_get(request, action_name):
    """Get specific action.

    :param action_name: Action name
    """

    return mistralclient(request).actions.get(action_name)


def action_create(request, action_definition):
    """Create action.

    :param action_definition: Action definition
    """

    return mistralclient(request).actions.create(action_definition)


def action_update(request, action_definition):
    """Update action.

    :param action_definition: Action definition
    """

    return mistralclient(request).actions.update(action_definition)


def action_run(request, action_name, input, params):
    """Run specific action execution.

    :param action_name: Action name
    :param input: input
    :param params: params
    """

    return mistralclient(request).action_executions.create(
        action_name,
        input,
        **params
    )


def action_delete(request, action_name):
    """Delete action.

    :param action_name: Action name
    """

    return mistralclient(request).actions.delete(action_name)


@handle_errors(_("Unable to retrieve action executions list"), [])
def action_executions_list(request, task_execution_id=None):
    """Returns all actions executions.

    :param request: Request data
    :param task_execution_id: (Optional) Task Execution ID to filter by
    """

    return mistralclient(request).action_executions.list(task_execution_id)


@handle_errors(_("Unable to retrieve action execution"), [])
def action_execution_get(request, action_execution_id):
    """Get specific action execution.

    :param action_execution_id: Action Execution ID
    """

    return mistralclient(request).action_executions.get(action_execution_id)


@handle_errors(_("Unable to delete action execution/s"), [])
def action_execution_delete(request, action_execution_id):
    """Delete action execution.

    :param action_execution_id: Action execution ID
    """

    return mistralclient(request).action_executions.delete(action_execution_id)


def action_execution_update(request, id, state=None, output=None):
    """Update action execution output and or state.

    :param id: action execution id
    :param output: action execution output
    :param state: action execution state
    """

    return mistralclient(request).action_executions.update(id, state, output)


@handle_errors(_("Unable to retrieve cron trigger list"), [])
def cron_trigger_list(request):
    """Returns all cron triggers.

    :param request: Request data
    """

    return mistralclient(request).cron_triggers.list()


@handle_errors(_("Unable to retrieve cron trigger"), [])
def cron_trigger_get(request, cron_trigger_name):
    """Get specific cron trigger.

    :param request: Request data
    :param cron_trigger_name: Cron trigger name
    """

    return mistralclient(request).cron_triggers.get(cron_trigger_name)


@handle_errors(_("Unable to delete cron trigger/s"), [])
def cron_trigger_delete(request, cron_trigger_name):
    """Delete Cron Trigger.

    :param request: Request data
    :param cron_trigger_name: Cron Trigger name
    """

    return mistralclient(request).cron_triggers.delete(cron_trigger_name)


def cron_trigger_create(
    request,
    cron_trigger_name,
    workflow_ID,
    workflow_input,
    workflow_params,
    pattern,
    first_time,
    count
):
    """Create Cron Trigger.

    :param request: Request data
    :param cron_trigger_name: Cron Trigger name
    :param workflow_ID: Workflow ID
    :param workflow_input: Workflow input
    :param workflow_params: Workflow params <* * * * *>
    :param pattern: <* * * * *>
    :param first_time:
           Date and time of the first execution <YYYY-MM-DD HH:MM>
    :param count: Number of wanted executions <integer>
    """

    return mistralclient(request).cron_triggers.create(
        cron_trigger_name,
        workflow_ID,
        workflow_input,
        workflow_params,
        pattern,
        first_time,
        count
    )
