require 'litany/mixins'
require 'litany/resource'

module Litany
  class CloudWatchAlarm < Resource

    cfn_type 'AWS::CloudWatch::Alarm'

    property :datapoints_to_alarm, nil, [nil, 1..1440]
    property :description, nil, [nil, String]
    property :label, nil, [nil, String]
    property :metric, nil, [String]
    property :missing_data, :breaching, [:ignore, :breaching, :not_breaching, :missing]
    property :namespace, nil, [String]
    property :operator, :less_than, [:less_than, :greater_than, :less_than_or_equal_to, :greater_than_or_equal_to]
    property :occurrences, 1, 1..1440
    property :period, 60, Array.new(1440, &proc { |i| (i + 1) * 60 })
    property :statistic, :minimum, [:sample_count, :average, :sum, :minimum, :maximum, 0.0..100.0]
    property :threshold, nil, [Float, Integer]
    property :unit, nil, [nil, :seconds, :microseconds, :milliseconds, :bytes, :kilobytes, :megabytes, :gigabytes, :terabytes, :bits, :kilobits, :megabits, :gigabits, :terabits, :percent, :count, :bytes_second, :kilobytes_second, :megabytes_second, :gigabytes_second, :terabytes_second, :bits_second, :kilobits_second, :megabits_second, :gigabits_second, :terabits_second, :count_second, :none]

    property_collection :dimension, [], [Hash], required: false

    resource_reference SNSTopic, :alarm_action, required: false
    resource_reference SNSTopic, :insufficient_action, required: false
    resource_reference SNSTopic, :ok_action, required: false

    validator :evaluation_period do
      raise "Total evaluation window may not exceed 86,400 seconds. Current: #{period} * #{occurrences} = #{period * occurrences}." if period * occurrences > 86_400
    end

    def finalize_resource
      # No auto namespacing when using alarms
      return if parent_resource.is_a?(Alarms)

      case parent_resource
        when Instance
          auto_namespace = 'AWS/EC2'
          auto_dimension = { Name: :InstanceId, Value: ref(parent_resource) }
        when ApplicationLoadBalancer
          auto_namespace = 'AWS/ApplicationELB'
          auto_dimension = { Name: :LoadBalancer, Value: ref(parent_resource, :LoadBalancerFullName) }
        when ApplicationLoadBalancerTargetGroup
          auto_namespace = 'AWS/ApplicationELB'
          auto_dimension = [
            { Name: :LoadBalancer, Value: ref(parent_resource.parent_resource, :LoadBalancerFullName) },
            { Name: :TargetGroup, Value: ref(parent_resource, :TargetGroupFullName) }
          ]
        when NetworkLoadBalancer
          auto_namespace = 'AWS/NetworkELB'
          auto_dimension = { Name: :LoadBalancer, Value: ref(parent_resource, :LoadBalancerFullName) }
        when NetworkLoadBalancerTargetGroup
          auto_namespace = 'AWS/NetworkELB'
          auto_dimension = [
            { Name: :LoadBalancer, Value: ref(parent_resource.parent_resource, :LoadBalancerFullName) },
            { Name: :TargetGroup, Value: ref(parent_resource, :TargetGroupFullName) }
          ]
        when KinesisStream
          auto_namespace = 'AWS/Kinesis'
          auto_dimension = { Name: :StreamName, Value: ref(parent_resource) }
        when SQSQueue
          auto_namespace = 'AWS/SQS'
          auto_dimension = { Name: :QueueName, Value: ref(parent_resource, :QueueName) }
        when ECS
          auto_namespace = 'AWS/ECS'
          auto_dimension = { Name: :ClusterName, Value: ref(parent_resource.cluster) }
        when ECSService
          auto_namespace = 'AWS/ECS'
          auto_dimension = { Name: :ServiceName, Value: ref(parent_resource) }
        when ElasticsearchDomain
          auto_namespace = 'AWS/ES'
          auto_dimension = [
            { Name: :DomainName, Value: ref(parent_resource) },
            { Name: :ClientId, Value: { Ref: 'AWS::AccountId' } },
          ]
        when ElastiCacheReplicationGroup
          auto_namespace = 'AWS/ElastiCache'
        when CloudWatchLogMetricFilter
          auto_dimension = []
          auto_namespace = parent_resource.namespace
        else
          warn "Unable to calculate dimensions for parent: #{parent_resource}."
      end

      namespace auto_namespace unless auto_namespace.nil? || set_namespace?
      dimension auto_dimension unless auto_dimension.nil? || set_dimensions?
    end

    # Unimplemented: Subscriptions, use sns_subscription
    # TODO: Revisit EvaluateLowSampleCountPercentile
    def properties
      props = {
        AlarmName: label || name,
        ComparisonOperator: "#{operator.pascal_case}Threshold",
        EvaluationPeriods: occurrences,
        MetricName: metric,
        Namespace: namespace,
        Period: period,
        TreatMissingData: missing_data.camel_case,
        Threshold: threshold
      }

      props[:Unit] = unit.pascal_case if set_unit?

      props[:ActionsEnabled] = set_alarm_action? || set_insufficient_action? || set_ok_action?
      props[:AlarmActions] = ref([alarm_action]) if set_alarm_action?
      props[:InsufficientDataActions] = ref([insufficient_action]) if set_insufficient_action?
      props[:OKActions] = ref([ok_action]) if set_ok_action?

      props[:AlarmDescription] = description if set_description?
      props[:ExtendedStatistic] = "p#{statistic.round(2)}" unless statistic.is_a?(Symbol)
      props[:Statistic] = statistic.pascal_case if statistic.is_a?(Symbol)

      props[:DatapointsToAlarm] = datapoints_to_alarm if set_datapoints_to_alarm?

      if set_dimensions?
        props[:Dimensions] = dimensions.collect do |dimension|
          # This is backwards compatibility code, ensures people who may have set these in previous version will still work
          if dimension.count == 2 && dimension.include?(:Name) && dimension.include?(:Value)
            dimension
          else
            dimension.collect { |name, value| { Name: name, Value: value } }
          end
        end.flatten
      end

      props
    end

  end
end
