#
#    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 json
import logging
import sys

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

import iso8601

from gnocchiclient import utils


LOG_DEP = logging.getLogger('deprecated')


class CliMetricWithResourceID(command.Command):
    def get_parser(self, prog_name):
        parser = super(CliMetricWithResourceID, self).get_parser(prog_name)
        parser.add_argument("--resource-id", "-r",
                            help="ID of the resource")
        return parser


class CliMetricList(lister.Lister):
    """List metrics."""

    COLS = ('id', 'archive_policy/name', 'name', 'unit', 'resource_id')

    def get_parser(self, prog_name):
        parser = super(CliMetricList, self).get_parser(prog_name)
        parser.add_argument("--limit", type=int, metavar="<LIMIT>",
                            help="Number of metrics 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 metric attribute "
                            "(example: user_id:desc-nullslast")
        return parser

    def take_action(self, parsed_args):
        metrics = utils.get_client(self).metric.list(
            **utils.get_pagination_options(parsed_args))
        for metric in metrics:
            utils.format_archive_policy(metric["archive_policy"])
            utils.format_move_dict_to_root(metric, "archive_policy")
        return utils.list2cols(self.COLS, metrics)


class DeprecatedCliMetricList(CliMetricList):
    """Deprecated: List metrics."""

    def take_action(self, parsed_args):
        LOG_DEP.warning('This command has been deprecated. '
                        'Please use "metric list" instead.')
        return super(DeprecatedCliMetricList, self).take_action(parsed_args)


class CliMetricShow(CliMetricWithResourceID, show.ShowOne):
    """Show a metric."""

    def get_parser(self, prog_name):
        parser = super(CliMetricShow, self).get_parser(prog_name)
        parser.add_argument("metric",
                            help="ID or name of the metric")
        return parser

    def take_action(self, parsed_args):
        metric = utils.get_client(self).metric.get(
            metric=parsed_args.metric,
            resource_id=parsed_args.resource_id)
        metric['archive_policy/name'] = metric["archive_policy"]["name"]
        del metric['archive_policy']
        del metric['created_by_user_id']
        del metric['created_by_project_id']
        utils.format_resource_for_metric(metric)
        return self.dict2columns(metric)


class DeprecatedCliMetricShow(CliMetricShow):
    """Deprecated: Show a metric."""

    def take_action(self, parsed_args):
        LOG_DEP.warning('This command has been deprecated. '
                        'Please use "metric show" instead.')
        return super(DeprecatedCliMetricShow, self).take_action(parsed_args)


class CliMetricCreateBase(show.ShowOne, CliMetricWithResourceID):
    def get_parser(self, prog_name):
        parser = super(CliMetricCreateBase, self).get_parser(prog_name)
        parser.add_argument("--archive-policy-name", "-a",
                            dest="archive_policy_name",
                            help="name of the archive policy")
        return parser


class CliMetricCreate(CliMetricCreateBase):
    """Create a metric."""

    def get_parser(self, prog_name):
        parser = super(CliMetricCreate, self).get_parser(prog_name)
        parser.add_argument("name", nargs='?',
                            metavar="METRIC_NAME",
                            help="Name of the metric")
        parser.add_argument("--unit", "-u",
                            help="unit of the metric")
        return parser

    def take_action(self, parsed_args):
        metric = utils.get_client(self).metric._create_new(
            archive_policy_name=parsed_args.archive_policy_name,
            name=parsed_args.name,
            resource_id=parsed_args.resource_id,
            unit=parsed_args.unit,
        )
        utils.format_resource_for_metric(metric)
        if 'archive_policy' in metric:
            metric['archive_policy/name'] = metric["archive_policy"]["name"]
            del metric['archive_policy']
        del metric['created_by_user_id']
        del metric['created_by_project_id']
        return self.dict2columns(metric)


class DeprecatedCliMetricCreate(CliMetricCreate):
    """Deprecated: Create a metric."""

    def take_action(self, parsed_args):
        LOG_DEP.warning('This command has been deprecated. '
                        'Please use "metric create" instead.')
        return super(DeprecatedCliMetricCreate, self).take_action(parsed_args)


class CliMetricDelete(CliMetricWithResourceID):
    """Delete a metric."""

    def get_parser(self, prog_name):
        parser = super(CliMetricDelete, self).get_parser(prog_name)
        parser.add_argument("metric", nargs='+',
                            help="IDs or names of the metric")
        return parser

    def take_action(self, parsed_args):
        for metric in parsed_args.metric:
            utils.get_client(self).metric.delete(
                metric=metric, resource_id=parsed_args.resource_id)


class DeprecatedCliMetricDelete(CliMetricDelete):
    """Deprecated: Delete a metric."""

    def take_action(self, parsed_args):
        LOG_DEP.warning('This command has been deprecated. '
                        'Please use "metric delete" instead.')
        return super(DeprecatedCliMetricDelete, self).take_action(parsed_args)


class CliMeasuresReturn(lister.Lister):
    def get_parser(self, prog_name):
        parser = super(CliMeasuresReturn, self).get_parser(prog_name)
        parser.add_argument("--utc", help="Return timestamps as UTC",
                            default=False,
                            action="store_true")
        return parser

    @staticmethod
    def format_measures_with_tz(parsed_args, measures):
        if parsed_args.utc:
            def t(x):
                return x
        else:
            t = utils.dt_to_localtz
        return [(t(dt).isoformat(), g, v) for dt, g, v in measures]


class CliMeasuresShow(CliMetricWithResourceID, CliMeasuresReturn,
                      lister.Lister):
    """Get measurements of a metric."""

    COLS = ('timestamp', 'granularity', 'value')

    def get_parser(self, prog_name):
        parser = super(CliMeasuresShow, self).get_parser(prog_name)
        parser.add_argument("metric",
                            help="ID or name of the metric")
        parser.add_argument("--aggregation",
                            help="aggregation to retrieve")
        parser.add_argument("--start",
                            type=utils.parse_date,
                            help="beginning of the period")
        parser.add_argument("--stop",
                            type=utils.parse_date,
                            help="end of the period")
        parser.add_argument("--granularity",
                            help="granularity to retrieve")
        parser.add_argument("--refresh", action="store_true",
                            help="force aggregation of all known measures")
        parser.add_argument("--resample",
                            help=("granularity to resample time-series to "
                                  "(in seconds)"))
        return parser

    def take_action(self, parsed_args):
        measures = utils.get_client(self).metric.get_measures(
            metric=parsed_args.metric,
            resource_id=parsed_args.resource_id,
            aggregation=parsed_args.aggregation,
            start=parsed_args.start,
            stop=parsed_args.stop,
            granularity=parsed_args.granularity,
            refresh=parsed_args.refresh,
            resample=parsed_args.resample
        )
        return self.COLS, self.format_measures_with_tz(parsed_args, measures)


class CliMeasuresAddBase(CliMetricWithResourceID):
    def get_parser(self, prog_name):
        parser = super(CliMeasuresAddBase, self).get_parser(prog_name)
        parser.add_argument("metric", help="ID or name of the metric")
        return parser


class CliMeasuresAdd(CliMeasuresAddBase):
    """Add measurements to a metric."""

    def measure(self, measure):
        timestamp, __, value = measure.rpartition("@")
        try:
            timestamp = utils.parse_date(timestamp).isoformat()
        except iso8601.iso8601.ParseError:
            # NOTE(sileht): return string as-is and let the server decide
            # if it's valid. (like +2hour, now, -5day)
            pass
        return {'timestamp': timestamp, 'value': float(value)}

    def get_parser(self, prog_name):
        parser = super(CliMeasuresAdd, self).get_parser(prog_name)
        parser.add_argument("-m", "--measure", action='append',
                            required=True, type=self.measure,
                            help=("timestamp and value of a measure "
                                  "separated with a '@'"))
        return parser

    def take_action(self, parsed_args):
        utils.get_client(self).metric.add_measures(
            metric=parsed_args.metric,
            resource_id=parsed_args.resource_id,
            measures=parsed_args.measure,
        )


class CliMeasuresBatch(command.Command):
    def stdin_or_file(self, value):
        if value == "-":
            return sys.stdin
        else:
            return open(value, 'r')

    def get_parser(self, prog_name):
        parser = super(CliMeasuresBatch, self).get_parser(prog_name)
        parser.add_argument("file", type=self.stdin_or_file,
                            help=("File containing measurements to batch or "
                                  "- for stdin (see Gnocchi REST API docs for "
                                  "the format"))
        return parser


class CliMetricsMeasuresBatch(CliMeasuresBatch):
    def take_action(self, parsed_args):
        with parsed_args.file as f:
            utils.get_client(self).metric.batch_metrics_measures(json.load(f))


class CliResourcesMetricsMeasuresBatch(CliMeasuresBatch):
    def get_parser(self, prog_name):
        parser = super(CliResourcesMetricsMeasuresBatch, self).get_parser(
            prog_name)
        parser.add_argument("--create-metrics", action='store_true',
                            help="Create unknown metrics"),
        return parser

    def take_action(self, parsed_args):
        with parsed_args.file as f:
            utils.get_client(self).metric.batch_resources_metrics_measures(
                json.load(f), create_metrics=parsed_args.create_metrics)


class CliMeasuresAggregation(CliMeasuresReturn):
    """Get measurements of aggregated metrics."""

    COLS = ('timestamp', 'granularity', 'value')

    def get_parser(self, prog_name):
        parser = super(CliMeasuresAggregation, self).get_parser(prog_name)
        parser.add_argument("-m", "--metric", nargs='+', required=True,
                            help="metrics IDs or metric name")
        parser.add_argument("--aggregation", help="granularity aggregation "
                                                  "function to retrieve")
        parser.add_argument("--reaggregation",
                            help="groupby aggregation function to retrieve")
        parser.add_argument("--start",
                            type=utils.parse_date,
                            help="beginning of the period")
        parser.add_argument("--stop",
                            type=utils.parse_date,
                            help="end of the period")
        parser.add_argument("--granularity",
                            help="granularity to retrieve")
        parser.add_argument("--needed-overlap", type=float,
                            help=("percent of datapoints in each "
                                  "metrics required"))
        utils.add_query_argument("--query", parser)
        parser.add_argument("--resource-type", default="generic",
                            help="Resource type to query"),
        parser.add_argument("--groupby",
                            action='append',
                            help="Attribute to use to group resources"),
        parser.add_argument("--refresh", action="store_true",
                            help="force aggregation of all known measures")
        parser.add_argument("--resample",
                            help=("granularity to resample time-series to "
                                  "(in seconds)"))
        parser.add_argument("--fill",
                            help=("Value to use when backfilling timestamps "
                                  "with missing values in a subset of series. "
                                  "Value should be a float or 'null'."))
        return parser

    def take_action(self, parsed_args):
        metrics = parsed_args.metric
        if parsed_args.query:
            if len(parsed_args.metric) != 1:
                raise ValueError("One metric is required if query is provided")
            metrics = parsed_args.metric[0]
        measures = utils.get_client(self).metric.aggregation(
            metrics=metrics,
            query=parsed_args.query,
            aggregation=parsed_args.aggregation,
            reaggregation=parsed_args.reaggregation,
            start=parsed_args.start,
            stop=parsed_args.stop,
            granularity=parsed_args.granularity,
            needed_overlap=parsed_args.needed_overlap,
            resource_type=parsed_args.resource_type,
            groupby=parsed_args.groupby,
            refresh=parsed_args.refresh,
            resample=parsed_args.resample, fill=parsed_args.fill
        )
        if parsed_args.groupby:
            ms = []
            for g in measures:
                group_name = ", ".join("%s: %s" % (k, g['group'][k])
                                       for k in sorted(g['group']))
                for m in g['measures']:
                    i = [group_name]
                    i.extend(self.format_measures_with_tz(parsed_args, [m])[0])
                    ms.append(i)
            return ('group',) + self.COLS, ms
        return self.COLS, self.format_measures_with_tz(parsed_args, measures)
