# frozen_string_literal: true

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

module Litany
  class BaseInstance < Resource;
  end

  class Instance < BaseInstance
    include Taggable

    cfn_type 'AWS::EC2::Instance'
    parent_resource :service

    property :disable_api_termination, false, [true, false]
    property :ephemeral_volumes, 0, 0..26
    property :create_elastic_ip, false, [true, false]
    property :hostname, nil, [nil, String]
    property :instance_initiated_shutdown_behavior, :stop, [:stop, :terminate]
    property :source_dest_check, true, [true, false]
    property :root_volume_size, 50, 4..16_384
    property :device_prefix, 'sd', ['sd', 'xvd']

    child_resource ElasticIp, :elastic_ip, automatic: :on_access

    resource_collection CloudWatchAlarm, :cloudwatch_alarm, required: false
    resource_collection DNSRecord, :dns_record, required: false
    resource_collection LambdaFunction, :lambda_function, required: false
    resource_collection Volume, :volume, required: false

    resource_reference Subnet, :subnet

    def finalize_resource
      hostname = self.hostname || "#{name}-#{active_environment.name}"
      meta :hostname, hostname

      return unless active_environment.set_dns_zone? || active_environment.set_dns_zone_reference?

      instance = self
      dns_zone = active_environment.dns_zone
      dns_zone_reference = active_environment.dns_zone_reference

      dns_record :"#{hostname}.#{dns_zone.name || dns_zone_reference.name}" do
        comment "Hostname record for #{resource_name}"
        target ref(instance, :PrivateIp)
        type :a
        zone dns_zone
        zone_reference dns_zone_reference
      end

      elastic_ip if create_elastic_ip
    end

    def properties
      # Unsupported / Won't Implement: Affinity, Tenancy, HostId, Ipv6AddressCount, Ipv6Addresses, KernelId, RamdiskId, PlacementGroupName, SsmAssociations, AdditionalInfo
      # Implementing in alternative ways: PrivateIpAddress, NetworkInterfaces.GroupSet, SecurityGroups

      props = {
        AvailabilityZone: subnet.zone,
        BlockDeviceMappings: [
          {
            DeviceName: "/dev/#{device_prefix}a#{device_prefix == 'sd' ? '1' : ''}",
            Ebs: {
              # Not setting encrypted here as it has to be inherited from the root AMI snapshot.
              DeleteOnTermination: true,
              VolumeSize: root_volume_size,
              VolumeType: :gp2
            }
          }
        ],
        DisableApiTermination: disable_api_termination,
        EbsOptimized: ebs_optimized,
        IamInstanceProfile: ref(instance_profile),
        ImageId: image.ami,
        InstanceInitiatedShutdownBehavior: instance_initiated_shutdown_behavior,
        InstanceType: instance_type,
        Monitoring: detailed_monitoring,
        SourceDestCheck: source_dest_check,
        SubnetId: ref(subnet)
      }

      props[:KeyName] = key_pair unless key_pair.nil?
      props[:SecurityGroupIds] = ref(security_groups) unless security_groups.empty?

      if ephemeral_volumes.positive?
        device_character = 'b'.ord

        props[:BlockDeviceMappings] += Array.new(ephemeral_volumes) do |index|
          {
            DeviceName: "/dev/#{device_prefix}#{(device_character + index).chr}",
            VirtualName: "ephemeral#{index}"
          }
        end
      end

      volumes.each do |volume|
        props[:Volumes] ||= []
        props[:Volumes] << {
          Device: volume.device,
          VolumeId: ref(volume)
        }
      end

      props[:UserData] = compile_userdata unless image.userdata.template.nil?

      props
    end

    def snapshot_interval(interval, retention: nil)
      return if active_environment.nil?

      case interval
        when :daily
          snapshot_rate = 1440
          retention ||= 14
        when :hourly
          snapshot_rate = 60
          retention ||= 3
        else
          error "Invalid snapshot interval :#{interval}. Currently supported: [:daily, :hourly]"
          exit 1
      end

      instance_name = output_name
      instance_region = active_environment.region
      lambda_function :"#{name}_daily_snapshot" do
        code 'create_snapshot.js.erb'
        rate snapshot_rate
        timeout 30
        meta :instance_name, instance_name
        meta :instance_region, instance_region
        meta :retention, retention
        no_default_environment true

        allow actions: ['ec2:DescribeInstances', 'ec2:DescribeVolumes', 'ec2:DescribeSnapshots', 'ec2:CreateSnapshot', 'ec2:DeleteSnapshot', 'ec2:CreateTags'],
              resource: '*'
      end
    end

    def default_tags
      {
        LitanyInstanceName: self.name,
        InstanceHostname: self.hostname || "#{name}-#{active_environment.name}",
        InstanceDNSZone: active_environment.dns_zone.name || active_environment.dns_zone_reference.name
      }
    end
  end
end
