# Copyright (c) 2013 Mirantis, 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.

from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from oslo_log import log as logging

from muranoclient.common import exceptions as exc
from muranodashboard import api
from muranodashboard.api import packages as packages_api
from muranodashboard.common import utils
from muranodashboard.environments import consts
from muranodashboard.environments import topology


KEY_ERROR_TEMPLATE = _(
    "Error fetching the environment. The page may be rendered incorrectly. "
    "Reason: %s")
LOG = logging.getLogger(__name__)


def get_status_messages_for_service(request, service_id, environment_id):
    client = api.muranoclient(request)
    deployments = client.deployments.list(environment_id)
    LOG.debug('Deployment::List {0}'.format(deployments))

    result = '\n'
    # TODO(efedorova): Add updated time to logs
    if deployments:
        for deployment in reversed(deployments):
            reports = client.deployments.reports(
                environment_id, deployment.id, service_id)

            for report in reports:
                result += (utils.adjust_datestr(request, report.created) +
                           ' - ' + report.text + '\n')
    return result


def create_session(request, environment_id):
    sessions = request.session.get('sessions', {})
    id = api.muranoclient(request).sessions.configure(environment_id).id
    sessions[environment_id] = id
    request.session['sessions'] = sessions
    return id


class Session(object):
    @staticmethod
    def get_or_create(request, environment_id):
        """Get an open session id

        Gets id from already opened session for specified environment,
        otherwise opens new session and returns its id

        :param request:
        :param environment_id:
        :return: Session Id
        """
        # We store opened sessions for each environment in dictionary per user
        sessions = request.session.get('sessions', {})

        if environment_id in sessions:
            id = sessions[environment_id]
        else:
            id = create_session(request, environment_id)
        return id

    @staticmethod
    def get_or_create_or_delete(request, environment_id):
        """Get an open session id

        Gets id from session in open state for specified environment.
        If state is deployed, then the session is deleted and a new one
        is created. If there are no sessions, then a new one is created.
        Returns id of chosen or created session.

        :param request:
        :param environment_id:
        :return: Session id
        """
        sessions = request.session.get('sessions', {})
        client = api.muranoclient(request)

        if environment_id in sessions:
            id = sessions[environment_id]
            try:
                session_data = client.sessions.get(environment_id, id)
            except exc.HTTPForbidden:
                del sessions[environment_id]
                LOG.debug("The environment is being deployed by other user. "
                          "Creating a new session "
                          "for the environment {0}".format(environment_id))
                return create_session(request, environment_id)
            else:
                if session_data.state in [consts.STATUS_ID_DEPLOY_FAILURE,
                                          consts.STATUS_ID_READY]:
                    del sessions[environment_id]
                    LOG.debug("The existing session has been already deployed."
                              " Creating a new session "
                              "for the environment {0}".format(environment_id))
                    return create_session(request, environment_id)
        else:
            LOG.debug("Creating a new session")
            return create_session(request, environment_id)
        LOG.debug("Found active session for the environment {0}"
                  .format(environment_id))
        return id

    @staticmethod
    def get_if_available(request, environment_id):
        """Get an id of open session if it exists and is not in deployed state.

        Returns None otherwise
        """
        sessions = request.session.get('sessions', {})
        client = api.muranoclient(request)

        if environment_id in sessions:
            id = sessions[environment_id]
            try:
                session_data = client.sessions.get(environment_id, id)
            except exc.HTTPForbidden:
                return None
            else:
                if session_data.state in [consts.STATUS_ID_DEPLOY_FAILURE,
                                          consts.STATUS_ID_READY]:
                    return None
                return id

    @staticmethod
    def get(request, environment_id):
        """Get an open session id

        Gets id from already opened session for specified environment,
        otherwise returns None

        :param request:
        :param environment_id:
        :return: Session Id
        """
        # We store opened sessions for each environment in dictionary per user
        sessions = request.session.get('sessions', {})
        session_id = sessions.get(environment_id, '')
        if session_id:
            LOG.debug("Using session_id {0} for the environment {1}".format(
                session_id, environment_id))
        else:
            LOG.debug("Session for the environment {0} not found".format(
                environment_id))
        return session_id

    @staticmethod
    def set(request, environment_id, session_id):
        """Set an open session id

        sets id from already opened session for specified environment.

        :param request:
        :param environment_id:
        :param session_id
        """
        # We store opened sessions for each environment in dictionary per user
        sessions = request.session.get('sessions', {})
        sessions[environment_id] = session_id
        request.session['sessions'] = sessions


def _update_env(env, request):
    # TODO(vakovalchuk): optimize latest deployment when limit is available
    deployments = deployments_list(request, env.id)
    if deployments:
        latest_deployment = deployments[0]
        try:
            deployed_services = {service['?']['id'] for service in
                                 latest_deployment.description['services']}
        except KeyError as e:
            deployed_services = set()
            exceptions.handle_recoverable(
                request, KEY_ERROR_TEMPLATE % e.message)
    else:
        deployed_services = set()

    if env.services:
        try:
            current_services = {service['?']['id'] for service in env.services}
        except KeyError as e:
            current_services = set()
            exceptions.handle_recoverable(
                request, KEY_ERROR_TEMPLATE % e.message)
    else:
        current_services = set()

    env.has_new_services = current_services != deployed_services

    if not env.has_new_services and env.status == consts.STATUS_ID_PENDING:
        env.status = consts.STATUS_ID_READY

    if not env.has_new_services and env.version == 0:
        if env.status == consts.STATUS_ID_READY:
            env.status = consts.STATUS_ID_NEW
    return env


def environments_list(request):
    environments = []
    client = api.muranoclient(request)
    with api.handled_exceptions(request):
        environments = client.environments.list()
    LOG.debug('Environment::List {0}'.format(environments))
    for index, env in enumerate(environments):
        environments[index] = environment_get(request, env.id)

    return environments


def environment_create(request, parameters):
    # name is required param
    body = {'name': parameters['name']}
    if 'defaultNetworks' in parameters:
        body['defaultNetworks'] = parameters['defaultNetworks']
    env = api.muranoclient(request).environments.create(body)
    LOG.debug('Environment::Create {0}'.format(env))
    return env


def environment_delete(request, environment_id, abandon=False):
    action = 'Abandon' if abandon else 'Delete'
    LOG.debug('Environment::{0} <Id : {1}>'.format(action, environment_id))
    return api.muranoclient(request).environments.delete(
        environment_id, abandon)


def environment_get(request, environment_id):
    session_id = Session.get(request, environment_id)
    LOG.debug('Environment::Get <Id: {0}, SessionId: {1}>'.
              format(environment_id, session_id))
    client = api.muranoclient(request)
    env = client.environments.get(environment_id, session_id)
    acquired = getattr(env, 'acquired_by', None)
    if acquired and acquired != session_id:
        env = client.environments.get(environment_id, acquired)
        Session.set(request, environment_id, acquired)

    env = _update_env(env, request)

    LOG.debug('Environment::Get {0}'.format(env))
    return env


def environment_deploy(request, environment_id):
    session_id = Session.get_or_create_or_delete(request, environment_id)
    LOG.debug('Session::Get <Id: {0}>'.format(session_id))
    env = api.muranoclient(request).sessions.deploy(environment_id, session_id)
    LOG.debug('Environment::Deploy <EnvId: {0}, SessionId: {1}>'
              ''.format(environment_id, session_id))
    return env


def environment_update(request, environment_id, name):
    return api.muranoclient(request).environments.update(environment_id, name)


def action_allowed(request, environment_id):
    env = environment_get(request, environment_id)
    status = getattr(env, 'status', None)
    return status not in ('deploying',)


def services_list(request, environment_id):
    """Get environment applications.

       This function collects data from Murano API and modifies it only for
       dashboard purposes. Those changes don't impact application
       deployment parameters.
    """
    def strip(msg, to=100):
        return u'%s...' % msg[:to] if len(msg) > to else msg

    services = []
    # need to create new session to see services deployed by other user
    session_id = Session.get(request, environment_id)

    get_environment = api.muranoclient(request).environments.get
    environment = get_environment(environment_id, session_id)
    try:
        client = api.muranoclient(request)
        reports = client.environments.last_status(environment_id, session_id)
    except exc.HTTPNotFound:
        LOG.exception(_('Could not retrieve latest status for '
                        'the {0} environment').format(environment_id))
        reports = {}

    for service_item in environment.services or []:
        service_data = service_item
        try:
            service_id = service_data['?']['id']
        except KeyError as e:
            exceptions.handle_recoverable(
                request, KEY_ERROR_TEMPLATE % e.message)
            continue

        if service_id in reports and reports[service_id]:
            last_operation = strip(reports[service_id].text)
            time = reports[service_id].updated
        else:
            last_operation = 'Component draft created' \
                if environment.version == 0 else ''
            try:
                time = service_data['updated'][:-7]
            except KeyError:
                time = None

        service_data['environment_id'] = environment_id
        service_data['environment_version'] = environment.version
        service_data['operation'] = last_operation
        service_data['operation_updated'] = time
        if service_data['?'].get('name'):
            service_data['name'] = service_data['?']['name']
        if (consts.DASHBOARD_ATTRS_KEY not in service_data['?'] or
                not service_data['?'][consts.DASHBOARD_ATTRS_KEY].get('name')):
            try:
                fqn = service_data['?']['type']
            except KeyError as e:
                exceptions.handle_recoverable(
                    request, KEY_ERROR_TEMPLATE % e.message)
                continue
            version = None
            if '/' in fqn:
                version, fqn = fqn.split('/')[1].split('@')
            pkg = packages_api.app_by_fqn(request, fqn, version=version)
            if pkg:
                app_name = pkg.name
                storage = service_data['?'].setdefault(
                    consts.DASHBOARD_ATTRS_KEY, {})
                storage['name'] = app_name

        services.append(service_data)

    LOG.debug('Service::List')
    return [utils.Bunch(**service) for service in services]


def service_list_by_fqns(request, environment_id, fqns):
    if environment_id is None:
        return []
    services = services_list(request, environment_id)
    LOG.debug('Service::Instances::List')
    try:
        services = [service for service in services
                    if service['?']['type'].split('/')[0] in fqns]
    except KeyError as e:
        services = []
        exceptions.handle_recoverable(request, KEY_ERROR_TEMPLATE % e.message)

    return services


def service_create(request, environment_id, parameters):
    # We should be able to delete session if we want to add new services to
    # this environment.
    session_id = Session.get_or_create_or_delete(request, environment_id)
    LOG.debug('Service::Create {0}'.format(parameters['?']['type']))
    return api.muranoclient(request).services.post(environment_id,
                                                   path='/',
                                                   data=parameters,
                                                   session_id=session_id)


def service_delete(request, environment_id, service_id):
    LOG.debug('Service::Delete <SrvId: {0}>'.format(service_id))
    session_id = Session.get_or_create_or_delete(request, environment_id)
    return api.muranoclient(request).services.delete(environment_id,
                                                     '/' + service_id,
                                                     session_id)


def service_get(request, environment_id, service_id):
    services = services_list(request, environment_id)
    LOG.debug("Return service detail for a specified id")
    try:
        for service in services:
            if service['?']['id'] == service_id:
                return service
    except KeyError as e:
        exceptions.handle_recoverable(request, KEY_ERROR_TEMPLATE % e.message)
    return None


def extract_actions_list(service):
    actions_data = service['?'].get('_actions', {})

    def make_action_datum(action_id, _action):
        return dict(_action.items() + [('id', action_id)])

    return [make_action_datum(_id, action) for (_id, action) in
            iter(actions_data.items()) if action.get('enabled')]


def run_action(request, environment_id, action_id):
    mc = api.muranoclient(request)
    return mc.actions.call(environment_id, action_id)


def deployments_list(request, environment_id):
    LOG.debug('Deployments::List')
    deployments = api.muranoclient(request).deployments.list(environment_id)

    LOG.debug('Environment::List {0}'.format(deployments))
    return deployments


def deployment_history(request):
    LOG.debug('Deployment::History')
    deployment_history = api.muranoclient(request).deployments.list(
        None, all_environments=True)

    for deployment in deployment_history:
        reports = deployment_reports(request, deployment.environment_id,
                                     deployment.id)
        deployment.reports = reports

    LOG.debug('Deployment::History {0}'.format(deployment_history))
    return deployment_history


def deployment_reports(request, environment_id, deployment_id):
    LOG.debug('Deployment::Reports::List')
    reports = api.muranoclient(request).deployments.reports(environment_id,
                                                            deployment_id)
    LOG.debug('Deployment::Reports::List {0}'.format(reports))
    return reports


def get_deployment_start(request, environment_id, deployment_id):
    deployments = api.muranoclient(request).deployments.list(environment_id)
    LOG.debug('Get deployment start time')
    for deployment in deployments:
        if deployment.id == deployment_id:
            return utils.adjust_datestr(request, deployment.started)
    return None


def get_deployment_descr(request, environment_id, deployment_id):
    deployments = api.muranoclient(request).deployments.list(environment_id)
    LOG.debug('Get deployment description')
    for deployment in deployments:
        if deployment.id == deployment_id:
            return deployment.description
    return None


def load_environment_data(request, environment_id):
    environment = environment_get(request, environment_id)
    return topology.render_d3_data(request, environment)
