# frozen_string_literal: true

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

module Litany
  class VPC < Resource
    include Taggable
    include Stack

    cfn_type 'AWS::EC2::VPC'
    id_validator /vpc-[0-9a-z]{8}/
    parent_resource :project

    child_resource InternetGateway, :internet_gateway

    output

    property :cidr, nil, [String, NetAddr::CIDR] do |cidr_block|
      cidr_obj = NetAddr::CIDR.create(cidr_block)
      raise "Cidr #{cidr_block} is invalid.  Calculated #{cidr_obj} as start of range." unless cidr_block == cidr_obj.to_s
      cidr_obj
    end

    property :min_cidr_partitions, 4, 1..8

    resource_collection SecurityGroup, :security_group, required: false
    resource_collection Subnet, :subnet
    resource_collection VPCEndpoint, :vpc_endpoint, required: false
    resource_collection VPCPeeringConnection, :peering_connection, required: false
    resource_collection VPNGateway, :vpn_gateway, required: false
    resource_collection VPCVPNGatewayAttachment, :vpn_attachment, required: false

    resource_references Environment, :environment

    def finalize_resource
      active_environment.dns_zone.vpc(self) if active_environment.set_dns_zone? && !active_environment.dns_zone.vpcs.include?(self)
      subnets.each { |subnet| subnet.cidr(next_cidr_partition) if subnet.cidr.nil? } unless cidr.nil?
    end

    # Creates a vpc endpoint for the desired service.  This can be used for both gateway and interface (PrivateLink) services.
    # @param endpoint_name [Symbol] a vpc-unique name for this service, for AWS services this will be used to specify which aws service to create an endpoint for
    # @param service: [nil, Symbol, String] (optional) generally only used when the endpoint_name is not suffecient to identify a service, accepts several forms
    #   * [Symbol] short AWS service names can be used if needed i.e. `:s3`
    #   * [String] a shortened version of the endpoint service name can be used i.e. `'vpce-svc-001bba2bd9629d73e'`
    #   * [String] a full reverse-dns notation service name can be used i.e. `'com.amazonaws.vpce.us-west-2.vpce-svc-001bba2bd9629d73e'`
    # @param dns: [nil, String] (optional) if provided this will create a route 53 zone attached to this vpc.  The zone is intended to shadow the exact dns name and allow for dns names to be consistent across multiple endpoints in different accounts
    # @param block [Block] (optional) used to further configure the endpoint
    def endpoint(endpoint_name, service: nil, dns: nil, &block)
      endpoint = vpc_endpoint("#{name}_#{endpoint_name}", &block)
      endpoint.service(service || endpoint_name) unless endpoint.set_service?
      return endpoint if active_environment.nil?

      [dns].flatten.each do |dns_name|
        zone = project.dns_zone :"#{name}_vpc_#{dns_name}"
        zone.environment(active_environment)
        zone.vpc(self)
        zone.zone_name(dns_name)
        zone.alias(zone.name, endpoint, alias_name: dns_name)
      end

      endpoint
    end

    def next_cidr_partition
      # Finds the next power of two for sane subnet division
      count = [2 ** (Math.log(subnets.size) / Math.log(2)).ceil, min_cidr_partitions].max
      @cidr_partitions ||= count == 1 ? [cidr] : cidr.subnet(IPCount: cidr.size / count).reverse
      @cidr_partitions.pop
    end

    def peer(peer_name, vpc: nil, account: nil, arn: nil, cidr: nil)
      peer = peering_connection :"#{name}_to_#{peer_name}" do
        target_vpc (vpc || peer_name)
        account account
        role arn
      end
      peer_route peer, cidr: cidr
    end

    def peer_route(peer, target: nil, cidr: nil)
      target ||= peer
      subnets.each do |subnet|
        subnet.route_table.add_route(peer.is_a?(Resource) ? peer.name : :"#{name}_to_#{peer}", cidr: cidr, target: target)
      end
    end

    def properties
      {
        CidrBlock: cidr,
        EnableDnsSupport: true,
        EnableDnsHostnames: true,
        InstanceTenancy: :default
      }
    end

    def vpn(name, gateway: nil)
      if gateway.nil?
        vpn_gateway name
      else
        vpn_attachment name do
          vpn_gateway gateway
        end
      end
    end
  end
end
