module Dalmatian
  class Cluster
    def initialize(description, helper: Helper, logger: Logger)
      properties = description.last
      @id = description.first
      @account_id = properties["account_id"]
      @environments = properties["environments"]
      @properties = properties["cluster"]
      @logger = logger
      @helper = helper
      @hosted_zones = build_hosted_zones(properties["hosted_zones"])
      @s3s = build_s3s(properties["s3"])
      @vpn_customer_gateways = build_vpn_customer_gateways(properties["vpn_customer_gateway"])
      @sources = build_sources(properties["dalmatian_config_source"])
      @services = build_services(properties["services"])
      @wafs = build_wafs(properties["waf"])
      @rdss = build_rdss(properties["rds"])
      @auroras = build_auroras(properties["aurora"])
      @elasticache_clusters = build_elasticache_clusters(properties["elasticache_cluster"])
      @opensearch_clusters = build_opensearch_clusters(properties["opensearch_cluster"])
      @shared_loadbalancers = build_shared_loadbalancers(properties["shared_loadbalancer"])
    end

    attr_reader :hosted_zones, :s3s, :vpn_customer_gateways, :sources, :services, :wafs, :rdss, :auroras, :elasticache_clusters,
      :opensearch_clusters, :shared_loadbalancers, :environments, :properties, :account_id, :id

    def name
      properties["name"] || id
    end

    def fetch(infrastructure_name: "all")
      sources.each do |source|
        next unless ["all", source.cluster_id].include? infrastructure_name

        return verify_source_path(source) unless remotely_held?(source)

        destination = "#{Infrastructure::PATH}/#{source.name}.config"
        fetch_source(source, destination)
      end
    end

    def deploy(environment_name: "all", service_name: "all", skip_deployments: "", test: false, auto_approve: false, plan: false, destroy: false, verbose: false)
      skip_deployments = skip_deployments.split(/\s*,\s*/)
      hosted_zones.each do |hosted_zone|
        unless skip_deployments.include?("hosted-zone")
          deploy_hosted_zone(hosted_zone, test, auto_approve, plan, destroy, verbose)
        end
      end
      s3s.each do |s3|
        unless skip_deployments.include?("s3")
          deploy_s3(s3, test, auto_approve, plan, destroy, verbose)
        end
      end
      vpn_customer_gateways.each do |vpn_customer_gateway|
        unless skip_deployments.include?("vpn-customer-gateway")
          deploy_vpn_customer_gateway(vpn_customer_gateway, test, auto_approve, plan, destroy, verbose)
        end
      end
      environments.each do |name, details|
        next unless ["all", name].include?(environment_name)
        environment = {name: name, details: details}
        deploy_cluster(environment, test, auto_approve, plan, destroy, verbose) if create? && !skip_deployments.include?("ecs")
        wafs.each do |waf|
          unless skip_deployments.include?("waf")
            deploy_waf(waf, environment, test, auto_approve, plan, destroy, verbose)
          end
        end
        rdss.each do |rds|
          unless skip_deployments.include?("rds")
            deploy_rds(rds, environment, test, auto_approve, plan, destroy, verbose)
          end
        end
        auroras.each do |aurora|
          unless skip_deployments.include?("aurora")
            deploy_aurora(aurora, environment, test, auto_approve, plan, destroy, verbose)
          end
        end
        elasticache_clusters.each do |elasticache_cluster|
          unless skip_deployments.include?("elasticache-cluster")
            deploy_elasticache_cluster(elasticache_cluster, environment, test, auto_approve, plan, destroy, verbose)
          end
        end
        opensearch_clusters.each do |opensearch_cluster|
          unless skip_deployments.include?("opensearch-cluster")
            deploy_opensearch_cluster(opensearch_cluster, environment, test, auto_approve, plan, destroy, verbose)
          end
        end
        services.each do |service|
          next unless service.to_params["launch_on"].include?(name)
          if [service.to_params["name"], "all"].include?(service_name) && !skip_deployments.include?("ecs-services")
            deploy_service(service, environment, test, auto_approve, plan, destroy, verbose)
          end
        end
        shared_loadbalancers.each do |shared_loadbalancer|
          unless skip_deployments.include?("shared-loadbalancer")
            deploy_shared_loadbalancer(shared_loadbalancer, environment, test, auto_approve, plan, destroy, verbose)
          end
        end
        sources.each do |source|
          deploy_source(source, environment, test, auto_approve, plan, destroy, verbose)
        end
      end
    end

    def target_directory
      File.join(Infrastructure::APP_ROOT, Infrastructure::PATH, "ecs")
    end

    private

    attr_reader :logger, :helper

    def build_hosted_zones(hosted_zones_references)
      (hosted_zones_references || []).map do |reference|
        HostedZone.new(cluster: self, reference: reference)
      end
    end

    def build_s3s(s3_references)
      (s3_references || []).map do |reference|
        S3.new(cluster: self, reference: reference)
      end
    end

    def build_vpn_customer_gateways(vpn_customer_gateway_references)
      (vpn_customer_gateway_references || []).map do |reference|
        VpnCustomerGateway.new(cluster: self, reference: reference)
      end
    end

    def build_sources(source_references)
      (source_references || []).map do |reference|
        Source.new(cluster: self, reference: reference)
      end
    end

    def build_services(service_references)
      (service_references || []).map do |reference|
        Service.new(cluster: self, reference: reference)
      end
    end

    def build_elasticache_clusters(elasticache_references)
      (elasticache_references || []).map do |reference|
        ElasticacheCluster.new(cluster: self, reference: reference)
      end
    end

    def build_opensearch_clusters(opensearch_references)
      (opensearch_references || []).map do |reference|
        OpensearchCluster.new(cluster: self, reference: reference)
      end
    end

    def build_wafs(waf_references)
      (waf_references || []).map do |reference|
        WAF.new(cluster: self, reference: reference)
      end
    end

    def build_rdss(rds_references)
      (rds_references || []).map do |reference|
        Rds.new(cluster: self, reference: reference)
      end
    end

    def build_auroras(aurora_references)
      (aurora_references || []).map do |reference|
        Aurora.new(cluster: self, reference: reference)
      end
    end

    def build_shared_loadbalancers(shared_loadbalancer_references)
      (shared_loadbalancer_references || []).map do |reference|
        SharedLoadbalancer.new(cluster: self, reference: reference)
      end
    end

    def deploy_hosted_zone(hosted_zone, test, auto_approve, plan, destroy, verbose)
      test_hosted_zone(hosted_zone) if test
      HostedZoneDeployment.new(
        hosted_zone: hosted_zone,
        plan: plan,
        auto_approve: auto_approve,
        destroy: destroy,
        verbose: verbose
      ).call
    end

    def deploy_s3(s3, test, auto_approve, plan, destroy, verbose)
      test_s3(s3) if test
      S3Deployment.new(
        s3: s3,
        plan: plan,
        auto_approve: auto_approve,
        destroy: destroy,
        verbose: verbose
      ).call
    end

    def deploy_vpn_customer_gateway(vpn_customer_gateway, test, auto_approve, plan, destroy, verbose)
      test_vpn_customer_gateway(vpn_customer_gateway) if test
      VpnCustomerGatewayDeployment.new(
        vpn_customer_gateway: vpn_customer_gateway,
        plan: plan,
        auto_approve: auto_approve,
        destroy: destroy,
        verbose: verbose
      ).call
    end

    def deploy_source(source, env, test, auto_approve, plan, destroy, verbose)
      test_source(source, env) if test
      SourceDeployment.new(
        source: source,
        env: env,
        plan: plan,
        auto_approve: auto_approve,
        destroy: destroy,
        verbose: verbose
      ).call
    end

    def deploy_cluster(env, test, auto_approve, plan, destroy, verbose)
      test_cluster(env) if test
      ClusterDeployment.new(
        cluster: self,
        env: env,
        plan: plan,
        auto_approve: auto_approve,
        destroy: destroy,
        verbose: verbose
      ).call
    end

    def deploy_service(service, env, test, auto_approve, plan, destroy, verbose)
      test_service(service, env) if test
      ServiceDeployment.new(
        service: service,
        env: env,
        plan: plan,
        auto_approve: auto_approve,
        destroy: destroy,
        verbose: verbose
      ).call
    end

    def deploy_waf(waf, env, test, auto_approve, plan, destroy, verbose)
      test_waf(waf, env) if test
      WAFDeployment.new(
        waf: waf,
        env: env,
        plan: plan,
        auto_approve: auto_approve,
        destroy: destroy,
        verbose: verbose
      ).call
    end

    def deploy_rds(rds, env, test, auto_approve, plan, destroy, verbose)
      test_rds(rds, env) if test
      RdsDeployment.new(
        rds: rds,
        env: env,
        plan: plan,
        auto_approve: auto_approve,
        destroy: destroy,
        verbose: verbose
      ).call
    end

    def deploy_aurora(aurora, env, test, auto_approve, plan, destroy, verbose)
      test_aurora(aurora, env) if test
      AuroraDeployment.new(
        aurora: aurora,
        env: env,
        plan: plan,
        auto_approve: auto_approve,
        destroy: destroy,
        verbose: verbose
      ).call
    end

    def deploy_elasticache_cluster(elasticache_cluster, env, test, auto_approve, plan, destroy, verbose)
      test_elasticache_cluster(elasticache_cluster, env) if test
      ElasticacheClusterDeployment.new(
        elasticache_cluster: elasticache_cluster,
        env: env,
        plan: plan,
        auto_approve: auto_approve,
        destroy: destroy,
        verbose: verbose
      ).call
    end

    def deploy_opensearch_cluster(opensearch_cluster, env, test, auto_approve, plan, destroy, verbose)
      test_opensearch_cluster(opensearch_cluster, env) if test
      OpensearchClusterDeployment.new(
        opensearch_cluster: opensearch_cluster,
        env: env,
        plan: plan,
        auto_approve: auto_approve,
        destroy: destroy,
        verbose: verbose
      ).call
    end

    def deploy_shared_loadbalancer(shared_loadbalancer, env, test, auto_approve, plan, destroy, verbose)
      test_shared_loadbalancer(shared_loadbalancer, env) if test
      SharedLoadbalancerDeployment.new(
        shared_loadbalancer: shared_loadbalancer,
        env: env,
        plan: plan,
        auto_approve: auto_approve,
        destroy: destroy,
        verbose: verbose
      ).call
    end

    def create?
      properties["create"]
    end

    def test_hosted_zone(hosted_zone)
      HostedZoneTest.new(hosted_zone: hosted_zone).call
    end

    def test_s3(s3)
      S3Test.new(s3: s3).call
    end

    def test_vpn_customer_gateway(vpn_customer_gateway)
      VpnCustomerGatewayTest.new(vpn_customer_gateway: vpn_customer_gateway).call
    end

    def test_cluster(env)
      ClusterTest.new(cluster: self, env: env).call
    end

    def test_source(source, env)
      SourceTest.new(source: source, env: env).call
    end

    def test_service(service, env)
      ServiceTest.new(service: service, env: env).call
    end

    def test_waf(waf, env)
      WAFTest.new(waf: waf, env: env).call
    end

    def test_rds(rds, env)
      RdsTest.new(rds: rds, env: env).call
    end

    def test_aurora(aurora, env)
      puts "debug Testing Aurora"
      AuroraTest.new(aurora: aurora, env: env).call
    end

    def test_elasticache_cluster(elasticache_cluster, env)
      ElasticacheClusterTest.new(elasticache_cluster: elasticache_cluster, env: env).call
    end

    def test_opensearch_cluster(opensearch_cluster, env)
      OpensearchClusterTest.new(opensearch_cluster: opensearch_cluster, env: env).call
    end

    def test_shared_loadbalancer(shared_loadbalancer, env)
      SharedLoadbalancerTest.new(shared_loadbalancer: shared_loadbalancer, env: env).call
    end

    def fetch_source(source, destination)
      logger.info("Cloning #{source.reference} ...")
      helper.remove(destination)
      helper.git_clone(source.reference, destination)
      helper.change_to(destination)
      helper.terrafile
      helper.change_to(Infrastructure::APP_ROOT)
    end

    def verify_source_path(source)
      if File.directory?(source.reference)
        logger.info "Found #{source.reference}"
      else
        logger.error "#{source.reference} does not exist"
      end
    end

    def remotely_held?(source)
      source.reference.start_with?("https://", "git@", "git://", "git+ssh://")
    end
  end
end
