require 'cloud_controller/diego/protocol/app_volume_mounts'
require 'cloud_controller/diego/protocol/container_network_info'
require 'cloud_controller/diego/protocol/routing_info'
require 'cloud_controller/diego/buildpack/desired_lrp_builder'
require 'cloud_controller/diego/docker/desired_lrp_builder'
require 'cloud_controller/diego/process_guid'
require 'cloud_controller/diego/ssh_key'
require 'credhub/config_helpers'
require 'models/helpers/health_check_types'
require 'cloud_controller/diego/main_lrp_action_builder.rb'

module VCAP::CloudController
  module Diego
    class AppRecipeBuilder
      include ::Diego::ActionBuilder

      METRIC_TAG_VALUE = ::Diego::Bbs::Models::MetricTagValue

      MONITORED_HEALTH_CHECK_TYPES = [HealthCheckTypes::PORT, HealthCheckTypes::HTTP, ''].map(&:freeze).freeze

      def initialize(config:, process:, ssh_key: SSHKey.new)
        @config  = config
        @process = process
        @ssh_key = ssh_key
      end

      def build_app_lrp
        ::Diego::Bbs::Models::DesiredLRP.new(app_lrp_arguments)
      end

      def build_app_lrp_update(existing_lrp)
        routes = generate_routes(routing_info)

        existing_routes = existing_lrp.routes
        ssh_route = existing_routes.routes[SSH_ROUTES_KEY]
        routes[SSH_ROUTES_KEY] = ssh_route if ssh_route

        ::Diego::Bbs::Models::DesiredLRPUpdate.new(
          instances:  process.instances,
          annotation: process.updated_at.to_f.to_s,
          routes:     ::Diego::Bbs::Models::ProtoRoutes.new(routes: routes)
        )
      end

      private

      attr_reader :config, :process, :ssh_key

      def app_lrp_arguments
        desired_lrp_builder = LifecycleProtocol.protocol_for_type(process.app.lifecycle_type).desired_lrp_builder(config, process)
        ports  = desired_lrp_builder.ports.dup
        routes = generate_routes(routing_info)

        if allow_ssh?
          ports << DEFAULT_SSH_PORT

          routes[SSH_ROUTES_KEY] = MultiJson.dump({
            container_port:   DEFAULT_SSH_PORT,
            private_key:      ssh_key.private_key,
            host_fingerprint: ssh_key.fingerprint
          })
        end
        {
          process_guid:                     Diego::ProcessGuid.from_process(process),
          instances:                        process.desired_instances,
          environment_variables:            desired_lrp_builder.global_environment_variables,
          start_timeout_ms:                 health_check_timeout_in_seconds * 1000,
          disk_mb:                          process.disk_quota,
          memory_mb:                        process.memory,
          privileged:                       desired_lrp_builder.privileged?,
          ports:                            ports,
          log_source:                       LRP_LOG_SOURCE,
          log_guid:                         process.app.guid,
          metrics_guid:                     process.app.guid,
          metric_tags:                      {
            'source_id' => METRIC_TAG_VALUE.new(static: process.app.guid),
            'process_id' => METRIC_TAG_VALUE.new(static: process.guid),
            'process_type' => METRIC_TAG_VALUE.new(static: process.type),
            'process_instance_id' => METRIC_TAG_VALUE.new(dynamic: METRIC_TAG_VALUE::DynamicValue::INSTANCE_GUID),
            'instance_id' => METRIC_TAG_VALUE.new(dynamic: METRIC_TAG_VALUE::DynamicValue::INDEX),
          },
          annotation:                       process.updated_at.to_f.to_s,
          egress_rules:                     Diego::EgressRules.new.running_protobuf_rules(process),
          cached_dependencies:              desired_lrp_builder.cached_dependencies,
          legacy_download_user:             desired_lrp_builder.action_user,
          trusted_system_certificates_path: RUNNING_TRUSTED_SYSTEM_CERT_PATH,
          network:                          generate_network,
          cpu_weight:                       TaskCpuWeightCalculator.new(memory_in_mb: process.memory).calculate,
          action:                           MainLRPActionBuilder.build(process, desired_lrp_builder, ssh_key),
          monitor:                          generate_monitor_action(desired_lrp_builder),
          root_fs:                          desired_lrp_builder.root_fs,
          setup:                            desired_lrp_builder.setup,
          image_layers:                     desired_lrp_builder.image_layers,
          domain:                           APP_LRP_DOMAIN,
          volume_mounts:                    generate_volume_mounts,
          PlacementTags:                    Array(IsolationSegmentSelector.for_space(process.space)),
          check_definition:                 generate_healthcheck_definition(desired_lrp_builder),
          routes:                           ::Diego::Bbs::Models::ProtoRoutes.new(routes: routes),
          max_pids:                         @config.get(:diego, :pid_limit),
          certificate_properties:           ::Diego::Bbs::Models::CertificateProperties.new(
            organizational_unit: [
              "organization:#{process.organization.guid}",
              "space:#{process.space.guid}",
              "app:#{process.app.guid}"
            ]
          ),
          image_username:                   process.desired_droplet.docker_receipt_username,
          image_password:                   process.desired_droplet.docker_receipt_password,
        }.compact
      end

      def routing_info
        @routing_info ||= Protocol::RoutingInfo.new(process).routing_info
      end

      def health_check_timeout_in_seconds
        process.health_check_timeout || config.get(:default_health_check_timeout)
      end

      def generate_routes(info)
        http_routes = (info['http_routes'] || []).map do |i|
          {
            hostnames:         [i['hostname']],
            port:              i['port'],
            route_service_url: i['route_service_url'],
            isolation_segment: IsolationSegmentSelector.for_space(process.space),
          }
        end

        {
          CF_ROUTES_KEY       => MultiJson.dump(http_routes),
          TCP_ROUTES_KEY      => MultiJson.dump((info['tcp_routes'] || [])),
          INTERNAL_ROUTES_KEY => MultiJson.dump((info['internal_routes'] || [])),
        }
      end

      def allow_ssh?
        process.enable_ssh
      end

      def generate_volume_mounts
        app_volume_mounts   = Protocol::AppVolumeMounts.new(process.app).as_json
        proto_volume_mounts = []

        app_volume_mounts.each do |volume_mount|
          proto_volume_mount = ::Diego::Bbs::Models::VolumeMount.new(
            driver:        volume_mount['driver'],
            container_dir: volume_mount['container_dir'],
            mode:          volume_mount['mode']
          )

          mount_config              = volume_mount['device']['mount_config'].present? ? volume_mount['device']['mount_config'].to_json : ''
          proto_volume_mount.shared = ::Diego::Bbs::Models::SharedDevice.new(
            volume_id:    volume_mount['device']['volume_id'],
            mount_config: mount_config
          )
          proto_volume_mounts.append(proto_volume_mount)
        end

        proto_volume_mounts
      end

      def generate_healthcheck_definition(lrp_builder)
        return unless MONITORED_HEALTH_CHECK_TYPES.include?(process.health_check_type)

        desired_ports = lrp_builder.ports
        checks        = []
        desired_ports.each_with_index do |port, index|
          checks << build_check(port, index)
        end

        ::Diego::Bbs::Models::CheckDefinition.new(checks: checks)
      end

      def build_check(port, index)
        timeout_ms = (process.health_check_invocation_timeout || 0) * 1000

        if process.health_check_type == HealthCheckTypes::HTTP && index == 0
          ::Diego::Bbs::Models::Check.new(http_check:
            ::Diego::Bbs::Models::HTTPCheck.new(
              path: process.health_check_http_endpoint,
              port: port,
              request_timeout_ms: timeout_ms
            )
          )
        else
          ::Diego::Bbs::Models::Check.new(tcp_check:
            ::Diego::Bbs::Models::TCPCheck.new(
              port: port,
              connect_timeout_ms: timeout_ms
            )
          )
        end
      end

      def generate_monitor_action(lrp_builder)
        return unless MONITORED_HEALTH_CHECK_TYPES.include?(process.health_check_type)

        desired_ports = lrp_builder.ports
        actions       = []
        desired_ports.each_with_index do |port, index|
          actions << build_action(lrp_builder, port, index)
        end

        action(timeout(parallel(actions), timeout_ms: 10.minutes.in_milliseconds))
      end

      def build_action(lrp_builder, port, index)
        extra_args = []
        if process.health_check_type == HealthCheckTypes::HTTP && index == 0
          extra_args << "-uri=#{process.health_check_http_endpoint}"
        end

        extra_args << "-timeout=#{process.health_check_invocation_timeout}s" if process.health_check_invocation_timeout

        ::Diego::Bbs::Models::RunAction.new(
          user:                lrp_builder.action_user,
          path:                '/tmp/lifecycle/healthcheck',
          args:                ["-port=#{port}"].concat(extra_args),
          resource_limits:     ::Diego::Bbs::Models::ResourceLimits.new(nofile: process.file_descriptors),
          log_source:          HEALTH_LOG_SOURCE,
          suppress_log_output: true,
        )
      end

      def generate_network
        Protocol::ContainerNetworkInfo.new(process.app, Protocol::ContainerNetworkInfo::APP).to_bbs_network
      end
    end
  end
end
