# -*- encoding: utf-8 -*-
#
# Copyright © 2013 New Dream Network, LLC (DreamHost)
#
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
#
# 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.
"""Model classes for use in the storage API.
"""


class Model(object):
    """Base class for storage API models.
    """

    def __init__(self, **kwds):
        self.fields = list(kwds)
        for k, v in kwds.iteritems():
            setattr(self, k, v)

    def as_dict(self):
        d = {}
        for f in self.fields:
            v = getattr(self, f)
            if isinstance(v, Model):
                v = v.as_dict()
            elif isinstance(v, list) and v and isinstance(v[0], Model):
                v = [sub.as_dict() for sub in v]
            d[f] = v
        return d

    def __eq__(self, other):
        return self.as_dict() == other.as_dict()


class Event(Model):
    """A raw event from the source system. Events have Traits.

       Metrics will be derived from one or more Events.
    """

    DUPLICATE = 1
    UNKNOWN_PROBLEM = 2

    def __init__(self, message_id, event_name, generated, traits):
        """Create a new event.

        :param message_id:  Unique ID for the message this event
                            stemmed from. This is different than
                            the Event ID, which comes from the
                            underlying storage system.
        :param event_name:  Name of the event.
        :param generated:   UTC time for when the event occured.
        :param traits:      list of Traits on this Event.
        """
        Model.__init__(self, message_id=message_id, event_name=event_name,
                       generated=generated, traits=traits)

    def append_trait(self, trait_model):
        self.traits.append(trait_model)

    def __repr__(self):
        trait_list = []
        if self.traits:
            trait_list = [str(trait) for trait in self.traits]
        return "<Event: %s, %s, %s, %s>" % \
            (self.message_id, self.event_name, self.generated,
             " ".join(trait_list))


class Trait(Model):
    """A Trait is a key/value pair of data on an Event. The value is variant
    record of basic data types (int, date, float, etc).
    """

    TEXT_TYPE = 1
    INT_TYPE = 2
    FLOAT_TYPE = 3
    DATETIME_TYPE = 4

    def __init__(self, name, dtype, value):
        Model.__init__(self, name=name, dtype=dtype, value=value)

    def __repr__(self):
        return "<Trait: %s %d %s>" % (self.name, self.dtype, self.value)


class Resource(Model):
    """Something for which sample data has been collected.
    """

    def __init__(self, resource_id, project_id,
                 first_sample_timestamp,
                 last_sample_timestamp,
                 source, user_id, metadata,
                 meter):
        """Create a new resource.

        :param resource_id: UUID of the resource
        :param project_id:  UUID of project owning the resource
        :param first_sample_timestamp: first sample timestamp captured
        :param last_sample_timestamp: last sample timestamp captured
        :param source:      the identifier for the user/project id definition
        :param user_id:     UUID of user owning the resource
        :param metadata:    most current metadata for the resource (a dict)
        :param meter:       list of the meters reporting data for the resource,
        """
        Model.__init__(self,
                       resource_id=resource_id,
                       first_sample_timestamp=first_sample_timestamp,
                       last_sample_timestamp=last_sample_timestamp,
                       project_id=project_id,
                       source=source,
                       user_id=user_id,
                       metadata=metadata,
                       meter=meter,
                       )


class ResourceMeter(Model):
    """The definitions of the meters for which data has been collected
    for a resource.

    See Resource.meter field.
    """

    def __init__(self, counter_name, counter_type, counter_unit):
        """Create a new resource meter.

        :param counter_name: the name of the counter updating the resource
        :param counter_type: one of gauge, delta, cumulative
        :param counter_unit: official units name for the sample data
        """
        Model.__init__(self,
                       counter_name=counter_name,
                       counter_type=counter_type,
                       counter_unit=counter_unit,
                       )


class Meter(Model):
    """Definition of a meter for which sample data has been collected.
    """

    def __init__(self, name, type, unit, resource_id, project_id, source,
                 user_id):
        """Create a new meter.

        :param name: name of the meter
        :param type: type of the meter (guage, counter)
        :param unit: unit of the meter
        :param resource_id: UUID of the resource
        :param project_id: UUID of project owning the resource
        :param source: the identifier for the user/project id definition
        :param user_id: UUID of user owning the resource
        """
        Model.__init__(self,
                       name=name,
                       type=type,
                       unit=unit,
                       resource_id=resource_id,
                       project_id=project_id,
                       source=source,
                       user_id=user_id,
                       )


class Sample(Model):
    """One collected data point.
    """
    def __init__(self,
                 source,
                 counter_name, counter_type, counter_unit, counter_volume,
                 user_id, project_id, resource_id,
                 timestamp, resource_metadata,
                 message_id,
                 message_signature,
                 ):
        """Create a new sample.

        :param source: the identifier for the user/project id definition
        :param counter_name: the name of the measurement being taken
        :param counter_type: the type of the measurement
        :param counter_unit: the units for the measurement
        :param counter_volume: the measured value
        :param user_id: the user that triggered the measurement
        :param project_id: the project that owns the resource
        :param resource_id: the thing on which the measurement was taken
        :param timestamp: the time of the measurement
        :param resource_metadata: extra details about the resource
        :param message_id: a message identifier
        :param message_signature: a hash created from the rest of the
                                  message data
        """
        Model.__init__(self,
                       source=source,
                       counter_name=counter_name,
                       counter_type=counter_type,
                       counter_unit=counter_unit,
                       counter_volume=counter_volume,
                       user_id=user_id,
                       project_id=project_id,
                       resource_id=resource_id,
                       timestamp=timestamp,
                       resource_metadata=resource_metadata,
                       message_id=message_id,
                       message_signature=message_signature)


class Statistics(Model):
    """Computed statistics based on a set of sample data.
    """
    def __init__(self, unit,
                 min, max, avg, sum, count,
                 period, period_start, period_end,
                 duration, duration_start, duration_end,
                 groupby):
        """Create a new statistics object.

        :param unit: The unit type of the data set
        :param min: The smallest volume found
        :param max: The largest volume found
        :param avg: The average of all volumes found
        :param sum: The total of all volumes found
        :param count: The number of samples found
        :param period: The length of the time range covered by these stats
        :param period_start: The timestamp for the start of the period
        :param period_end: The timestamp for the end of the period
        :param duration: The total time for the matching samples
        :param duration_start: The earliest time for the matching samples
        :param duration_end: The latest time for the matching samples
        :param groupby: The fields used to group the samples.
        """
        Model.__init__(self, unit=unit,
                       min=min, max=max, avg=avg, sum=sum, count=count,
                       period=period, period_start=period_start,
                       period_end=period_end, duration=duration,
                       duration_start=duration_start,
                       duration_end=duration_end,
                       groupby=groupby)


class Alarm(Model):
    ALARM_INSUFFICIENT_DATA = 'insufficient data'
    ALARM_OK = 'ok'
    ALARM_ALARM = 'alarm'

    ALARM_ACTIONS_MAP = {
        ALARM_INSUFFICIENT_DATA: 'insufficient_data_actions',
        ALARM_OK: 'ok_actions',
        ALARM_ALARM: 'alarm_actions',
    }

    """
    An alarm to monitor.

    :param alarm_id: UUID of the alarm
    :param name: The Alarm name
    :param description: User friendly description of the alarm
    :param enabled: Is the alarm enabled
    :param state: Alarm state (alarm/nodata/ok)
    :param meter_name: The counter that the alarm is based on
    :param comparison_operator: How to compare the samples and the threshold
    :param threshold: the value to compare to the samples
    :param statistic: the function from Statistic (min/max/avg/count)
    :param user_id: the owner/creator of the alarm
    :param project_id: the project_id of the creator
    :param evaluation_periods: the number of periods
    :param period: the time period in seconds
    :param timestamp: the timestamp when the alarm was last updated
    :param state_timestamp: the timestamp of the last state change
    :param ok_actions: the list of webhooks to call when entering the ok state
    :param alarm_actions: the list of webhooks to call when entering the
                          alarm state
    :param insufficient_data_actions: the list of webhooks to call when
                                      entering the insufficient data state
    :param matching_metadata: the key/values of metadata to match on.
    :param repeat_actions: Is the actions should be triggered on each
                           alarm evaluation.
    """
    def __init__(self, alarm_id, name, meter_name,
                 comparison_operator, threshold, statistic,
                 user_id, project_id,
                 evaluation_periods=1,
                 period=60,
                 enabled=True,
                 description='',
                 timestamp=None,
                 state=ALARM_INSUFFICIENT_DATA,
                 state_timestamp=None,
                 ok_actions=[],
                 alarm_actions=[],
                 insufficient_data_actions=[],
                 matching_metadata={},
                 repeat_actions=False
                 ):
        if not description:
            # make a nice user friendly description by default
            description = 'Alarm when %s is %s a %s of %s over %s seconds' % (
                meter_name, comparison_operator,
                statistic, threshold, period)

        Model.__init__(
            self,
            alarm_id=alarm_id,
            enabled=enabled,
            name=name,
            description=description,
            timestamp=timestamp,
            meter_name=meter_name,
            user_id=user_id,
            project_id=project_id,
            comparison_operator=comparison_operator,
            threshold=threshold,
            statistic=statistic,
            evaluation_periods=evaluation_periods,
            period=period,
            state=state,
            state_timestamp=state_timestamp,
            ok_actions=ok_actions,
            alarm_actions=alarm_actions,
            insufficient_data_actions=
            insufficient_data_actions,
            repeat_actions=repeat_actions,
            matching_metadata=matching_metadata)


class AlarmChange(Model):
    """Record of an alarm change.

    :param event_id: UUID of the change event
    :param alarm_id: UUID of the alarm
    :param type: The type of change
    :param detail: JSON fragment describing change
    :param user_id: the user ID of the initiating identity
    :param project_id: the project ID of the initiating identity
    :param on_behalf_of: the tenant on behalf of which the change
                         is being made
    :param timestamp: the timestamp of the change
    """

    CREATION = 'creation'
    RULE_CHANGE = 'rule change'
    STATE_TRANSITION = 'state transition'
    DELETION = 'deletion'

    def __init__(self,
                 event_id,
                 alarm_id,
                 type,
                 detail,
                 user_id,
                 project_id,
                 on_behalf_of,
                 timestamp=None
                 ):
        Model.__init__(
            self,
            event_id=event_id,
            alarm_id=alarm_id,
            type=type,
            detail=detail,
            user_id=user_id,
            project_id=project_id,
            on_behalf_of=on_behalf_of,
            timestamp=timestamp)
