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

from cliff import command
from cliff import lister
from cliff import show

from gnocchiclient import exceptions
from gnocchiclient import utils


class CliResourceList(lister.Lister):
    """List resources."""

    COLS = ('id', 'type',
            'project_id', 'user_id',
            'original_resource_id',
            'started_at', 'ended_at',
            'revision_start', 'revision_end')

    def get_parser(self, prog_name, history=True):
        parser = super(CliResourceList, self).get_parser(prog_name)
        parser.add_argument("--details", action='store_true',
                            help="Show all attributes of generic resources"),
        if history:
            parser.add_argument("--history", action='store_true',
                                help="Show history of the resources"),
        parser.add_argument("--limit", type=int, metavar="<LIMIT>",
                            help="Number of resources to return "
                            "(Default is server default)")
        parser.add_argument("--marker", metavar="<MARKER>",
                            help="Last item of the previous listing. "
                            "Return the next results after this value")
        parser.add_argument("--sort", action="append", metavar="<SORT>",
                            help="Sort of resource attribute "
                            "(example: user_id:desc-nullslast")
        parser.add_argument("--type", "-t", dest="resource_type",
                            default="generic", help="Type of resource")
        return parser

    def _list2cols(self, resources):
        """Return a formatted list of resources."""
        if not resources:
            return self.COLS, []
        cols = list(self.COLS)
        for k in resources[0]:
            if k not in cols:
                cols.append(k)
        if 'creator' in cols:
            cols.remove('created_by_user_id')
            cols.remove('created_by_project_id')
        return utils.list2cols(cols, resources)

    def take_action(self, parsed_args):
        resources = utils.get_client(self).resource.list(
            resource_type=parsed_args.resource_type,
            **utils.get_pagination_options(parsed_args))
        # Do not dump metrics because it makes the list way too long
        for r in resources:
            del r['metrics']
        return self._list2cols(resources)


class CliResourceHistory(CliResourceList):
    """Show the history of a resource."""

    def get_parser(self, prog_name):
        parser = super(CliResourceHistory, self).get_parser(prog_name,
                                                            history=False)
        parser.add_argument("resource_id",
                            help="ID of a resource")
        return parser

    def take_action(self, parsed_args):
        resources = utils.get_client(self).resource.history(
            resource_type=parsed_args.resource_type,
            resource_id=parsed_args.resource_id,
            **utils.get_pagination_options(parsed_args))
        if parsed_args.formatter == 'table':
            return self._list2cols(list(map(normalize_metrics, resources)))
        return self._list2cols(resources)


class CliResourceSearch(CliResourceList):
    """Search resources with specified query rules."""

    def get_parser(self, prog_name):
        parser = super(CliResourceSearch, self).get_parser(prog_name)
        utils.add_query_argument("query", parser)
        return parser

    def take_action(self, parsed_args):
        resources = utils.get_client(self).resource.search(
            resource_type=parsed_args.resource_type,
            query=parsed_args.query,
            **utils.get_pagination_options(parsed_args))
        # Do not dump metrics because it makes the list way too long
        for r in resources:
            del r['metrics']
        return self._list2cols(resources)


def normalize_metrics(res):
    res['metrics'] = "\n".join(sorted(
        ["%s: %s" % (name, _id)
            for name, _id in res['metrics'].items()]))
    return res


class CliResourceShow(show.ShowOne):
    """Show a resource."""

    def get_parser(self, prog_name):
        parser = super(CliResourceShow, self).get_parser(prog_name)
        parser.add_argument("--type", "-t", dest="resource_type",
                            default="generic", help="Type of resource")
        parser.add_argument("resource_id",
                            help="ID of a resource")
        return parser

    def take_action(self, parsed_args):
        res = utils.get_client(self).resource.get(
            resource_type=parsed_args.resource_type,
            resource_id=parsed_args.resource_id)
        if parsed_args.formatter == 'table':
            normalize_metrics(res)
        return self.dict2columns(res)


class CliResourceCreate(show.ShowOne):
    """Create a resource."""

    def get_parser(self, prog_name):
        parser = super(CliResourceCreate, self).get_parser(prog_name)
        parser.add_argument("--type", "-t", dest="resource_type",
                            default="generic", help="Type of resource")
        parser.add_argument("resource_id",
                            help="ID of the resource")
        parser.add_argument("-a", "--attribute", action='append',
                            default=[],
                            help=("name and value of an attribute "
                                  "separated with a ':'"))
        parser.add_argument("-m", "--add-metric", action='append',
                            default=[],
                            help="name:id of a metric to add"),
        parser.add_argument(
            "-n", "--create-metric", action='append', default=[],
            help="name:archive_policy_name of a metric to create"),
        return parser

    def _resource_from_args(self, parsed_args, update=False):
        # Get the resource type to set the correct type
        rt_attrs = utils.get_client(self).resource_type.get(
            name=parsed_args.resource_type)['attributes']
        resource = {}
        if not update:
            resource['id'] = parsed_args.resource_id
        if parsed_args.attribute:
            for attr in parsed_args.attribute:
                attr, __, value = attr.partition(":")
                attr_type = rt_attrs.get(attr, {}).get('type')
                if attr_type == "number":
                    value = float(value)
                elif attr_type == "bool":
                    value = bool(distutils.util.strtobool(value))
                resource[attr] = value
        if (parsed_args.add_metric or
           parsed_args.create_metric or
           (update and parsed_args.delete_metric)):
            if update:
                r = utils.get_client(self).resource.get(
                    parsed_args.resource_type,
                    parsed_args.resource_id)
                default = r['metrics']
                for metric_name in parsed_args.delete_metric:
                    try:
                        del default[metric_name]
                    except KeyError:
                        raise exceptions.MetricNotFound(
                            message="Metric name %s not found" % metric_name)
            else:
                default = {}
            resource['metrics'] = default
            for metric in parsed_args.add_metric:
                name, _, value = metric.partition(":")
                resource['metrics'][name] = value
            for metric in parsed_args.create_metric:
                name, _, value = metric.partition(":")
                if value is "":
                    resource['metrics'][name] = {}
                else:
                    resource['metrics'][name] = {'archive_policy_name': value}

        return resource

    def take_action(self, parsed_args):
        resource = self._resource_from_args(parsed_args)
        res = utils.get_client(self).resource.create(
            resource_type=parsed_args.resource_type, resource=resource)
        if parsed_args.formatter == 'table':
            normalize_metrics(res)
        return self.dict2columns(res)


class CliResourceUpdate(CliResourceCreate):
    """Update a resource."""

    def get_parser(self, prog_name):
        parser = super(CliResourceUpdate, self).get_parser(prog_name)
        parser.add_argument("-d", "--delete-metric", action='append',
                            default=[],
                            help="Name of a metric to delete"),
        return parser

    def take_action(self, parsed_args):
        resource = self._resource_from_args(parsed_args, update=True)
        res = utils.get_client(self).resource.update(
            resource_type=parsed_args.resource_type,
            resource_id=parsed_args.resource_id,
            resource=resource)
        if parsed_args.formatter == 'table':
            normalize_metrics(res)
        return self.dict2columns(res)


class CliResourceDelete(command.Command):
    """Delete a resource."""

    def get_parser(self, prog_name):
        parser = super(CliResourceDelete, self).get_parser(prog_name)
        parser.add_argument("resource_id",
                            help="ID of the resource")
        return parser

    def take_action(self, parsed_args):
        utils.get_client(self).resource.delete(parsed_args.resource_id)


class CliResourceBatchDelete(show.ShowOne):
    """Delete a batch of resources based on attribute values."""

    def get_parser(self, prog_name):
        parser = super(CliResourceBatchDelete, self).get_parser(prog_name)
        parser.add_argument("--type", "-t", dest="resource_type",
                            default="generic", help="Type of resource")
        utils.add_query_argument("query", parser)
        return parser

    def take_action(self, parsed_args):
        res = utils.get_client(self).resource.batch_delete(
            resource_type=parsed_args.resource_type,
            query=parsed_args.query)
        return self.dict2columns(res)
