#   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.
#

"""Orchestration v1 Stack action implementations"""

import logging

from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib.i18n import _
from osc_lib import utils
from oslo_serialization import jsonutils
from urllib import request

from heatclient.common import format_utils
from heatclient.common import utils as heat_utils
from heatclient import exc as heat_exc


class ResourceShow(command.ShowOne):
    """Display stack resource."""

    log = logging.getLogger(__name__ + '.ResourceShowStack')

    def get_parser(self, prog_name):
        parser = super(ResourceShow, self).get_parser(prog_name)
        parser.add_argument(
            'stack',
            metavar='<stack>',
            help=_('Name or ID of stack to query')
        )
        parser.add_argument(
            'resource',
            metavar='<resource>',
            help=_('Name of resource')
        )
        parser.add_argument(
            '--with-attr',
            metavar='<attribute>',
            action='append',
            help=_('Attribute to show, can be specified multiple times')
        )
        return parser

    def take_action(self, parsed_args):
        self.log.debug('take_action(%s)', parsed_args)

        client = self.app.client_manager.orchestration

        try:
            resource = client.resources.get(parsed_args.stack,
                                            parsed_args.resource,
                                            with_attr=parsed_args.with_attr)
        except heat_exc.HTTPNotFound:
            msg = (_('Stack or resource not found: %(stack)s %(resource)s') %
                   {'stack': parsed_args.stack,
                    'resource': parsed_args.resource})
            raise exc.CommandError(msg)

        return self.dict2columns(resource.to_dict())


class ResourceList(command.Lister):
    """List stack resources."""

    log = logging.getLogger(__name__ + '.ResourceListStack')

    @property
    def formatter_namespace(self):
        return 'heatclient.resource.formatter.list'

    def get_parser(self, prog_name):
        parser = super(ResourceList, self).get_parser(prog_name)
        parser.add_argument(
            'stack',
            metavar='<stack>',
            help=_('Name or ID of stack to query')
        )
        parser.add_argument(
            '--long',
            action='store_true',
            help=_('Enable detailed information presented for each resource '
                   'in resource list')
        )
        parser.add_argument(
            '-n', '--nested-depth',
            metavar='<nested-depth>',
            type=int,
            help=_('Depth of nested stacks from which to display resources')
        )

        parser.add_argument(
            '--filter',
            metavar='<key=value>',
            action='append',
            help=_('Filter parameters to apply on returned resources based on '
                   'their name, status, type, action, id and '
                   'physical_resource_id')
        )

        return parser

    def take_action(self, parsed_args):
        self.log.debug('take_action(%s)', parsed_args)

        client = self.app.client_manager.orchestration

        fields = {
            'nested_depth': parsed_args.nested_depth,
            'with_detail': parsed_args.long,
            'filters': heat_utils.format_parameters(parsed_args.filter),
        }

        try:
            resources = client.resources.list(parsed_args.stack, **fields)
        except heat_exc.HTTPNotFound:
            msg = _('Stack not found: %s') % parsed_args.stack
            raise exc.CommandError(msg)

        if parsed_args.formatter == 'dot':
            return [], resources

        columns = ['physical_resource_id', 'resource_type', 'resource_status',
                   'updated_time']

        if len(resources) >= 1 and not hasattr(resources[0], 'resource_name'):
            columns.insert(0, 'logical_resource_id')
        else:
            columns.insert(0, 'resource_name')

        if parsed_args.nested_depth or parsed_args.long:
            columns.append('stack_name')

        return (
            columns,
            (utils.get_item_properties(r, columns) for r in resources)
        )


class ResourceMetadata(format_utils.JsonFormat):
    """Show resource metadata"""

    log = logging.getLogger(__name__ + ".ResourceMetadata")

    def get_parser(self, prog_name):
        parser = super(ResourceMetadata, self).get_parser(prog_name)
        parser.add_argument(
            'stack',
            metavar='<stack>',
            help=_('Stack to display (name or ID)'),
        )
        parser.add_argument(
            'resource',
            metavar='<resource>',
            help=_('Name of the resource to show the metadata for'))
        return parser

    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)

        heat_client = self.app.client_manager.orchestration
        return _resource_metadata(heat_client, parsed_args)


def _resource_metadata(heat_client, args):
    fields = {'stack_id': args.stack,
              'resource_name': args.resource}
    try:
        metadata = heat_client.resources.metadata(**fields)
    except heat_exc.HTTPNotFound:
        raise exc.CommandError(_('Stack %(stack)s or resource %(resource)s '
                                 'not found.') %
                               {'stack': args.stack,
                                'resource': args.resource})

    data = list(metadata.values())
    columns = list(metadata.keys())
    return columns, data


class ResourceSignal(command.Command):
    """Signal a resource with optional data."""

    log = logging.getLogger(__name__ + ".ResourceSignal")

    def get_parser(self, prog_name):
        parser = super(ResourceSignal, self).get_parser(prog_name)
        parser.add_argument(
            'stack',
            metavar='<stack>',
            help=_('Name or ID of stack the resource belongs to'),
        )
        parser.add_argument(
            'resource',
            metavar='<resource>',
            help=_('Name of the resoure to signal'),
        )
        parser.add_argument(
            '--data',
            metavar='<data>',
            help=_('JSON Data to send to the signal handler')
        )
        parser.add_argument(
            '--data-file',
            metavar='<data-file>',
            help=_('File containing JSON data to send to the signal handler')
        )
        return parser

    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)

        heat_client = self.app.client_manager.orchestration
        return _resource_signal(heat_client, parsed_args)


def _resource_signal(heat_client, args):
    fields = {'stack_id': args.stack,
              'resource_name': args.resource}
    data = args.data
    data_file = args.data_file
    if data and data_file:
        raise exc.CommandError(_('Should only specify one of data or '
                                 'data-file'))

    if data_file:
        data_url = heat_utils.normalise_file_path_to_url(data_file)
        data = request.urlopen(data_url).read()

    if data:
        try:
            data = jsonutils.loads(data)
        except ValueError as ex:
            raise exc.CommandError(_('Data should be in JSON format: %s') % ex)
        if not isinstance(data, dict):
            raise exc.CommandError(_('Data should be a JSON dict'))

        fields['data'] = data
    try:
        heat_client.resources.signal(**fields)
    except heat_exc.HTTPNotFound:
        raise exc.CommandError(_('Stack %(stack)s or resource %(resource)s '
                                 'not found.') %
                               {'stack': args.stack,
                                'resource': args.resource})


class ResourceMarkUnhealthy(command.Command):
    """Set resource's health."""

    log = logging.getLogger(__name__ + ".ResourceMarkUnhealthy")

    def get_parser(self, prog_name):
        parser = super(ResourceMarkUnhealthy, self).get_parser(prog_name)
        parser.add_argument(
            'stack',
            metavar='<stack>',
            help=_('Name or ID of stack the resource belongs to')
        )
        parser.add_argument(
            'resource',
            metavar='<resource>',
            help=_('Name of the resource')
        )
        parser.add_argument(
            'reason',
            default="",
            nargs='?',
            help=_('Reason for state change')
        )
        parser.add_argument(
            '--reset',
            default=False,
            action="store_true",
            help=_('Set the resource as healthy')
        )
        return parser

    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)

        heat_client = self.app.client_manager.orchestration
        fields = {'stack_id': parsed_args.stack,
                  'resource_name': parsed_args.resource,
                  'mark_unhealthy': not parsed_args.reset,
                  'resource_status_reason': parsed_args.reason}
        try:
            heat_client.resources.mark_unhealthy(**fields)
        except heat_exc.HTTPNotFound:
            raise exc.CommandError(_('Stack or resource not found: '
                                     '%(id)s %(resource)s') %
                                   {'id': parsed_args.stack,
                                    'resource': parsed_args.resource})
