module Kontena::Cli::Stacks::YAML
 module Validations
   module CustomValidators
     require_relative 'custom_validators/affinities_validator'
     require_relative 'custom_validators/build_validator'
     require_relative 'custom_validators/extends_validator'
     require_relative 'custom_validators/hooks_validator'
     require_relative 'custom_validators/secrets_validator'
     require_relative 'custom_validators/certificates_validator'

     def self.load
       return if @loaded
       HashValidator.append_validator(AffinitiesValidator.new)
       HashValidator.append_validator(BuildValidator.new)
       HashValidator.append_validator(ExtendsValidator.new)
       HashValidator.append_validator(SecretsValidator.new)
       HashValidator.append_validator(CertificatesValidator.new)
       HashValidator.append_validator(HooksValidator.new)
       @loaded = true
     end
   end

   def common_validations
     {
        'image' => optional('string'), # it's optional because some base yml file might contain image option
        'extends' => optional('stacks_valid_extends'),
        'stateful' => optional('boolean'),
        'affinity' => optional('stacks_valid_affinities'),
        'cap_add' => optional('array'),
        'cap_drop' => optional('array'),
        'command' => optional('string'),
        'cpus' => optional('float'),
        'cpu_shares' => optional('integer'),
        'external_links' => optional('array'),
        'mem_limit' => optional('string'),
        'mem_swaplimit' => optional('string'),
        'shm_size' => optional('string'),
        'environment' => optional(-> (value) {
          if value.is_a?(Hash)
            value.all? do |k,v|
              k.kind_of?(String) && (
                v.kind_of?(String) ||
                v.kind_of?(Integer) ||
                v.kind_of?(TrueClass) ||
                v.kind_of?(FalseClass) ||
                v.nil?
              )
            end
          elsif value.is_a?(Array)
            value.all? { |v| v.kind_of?(String) && v =~ /\A[^=]+=/ }
          else
            false
          end
        }),
        'env_file' => optional(-> (value) { value.is_a?(String) || value.is_a?(Array) }),
        'instances' => optional('integer'),
        'links' => optional(-> (value) { value.is_a?(Array) || value.nil? }),
        'ports' => optional(-> (value) { value.is_a?(Array) && value.all? { |v| v.is_a?(String) && v.match(/\A(\d+\.\d+\.\d+\.\d+)?:?(\d+)\:(\d+)\/?(\w+)?\z/) } }),
        'pid' => optional('string'),
        'privileged' => optional('boolean'),
        'user' => optional('string'),
        'volumes' => optional('array'),
        'volumes_from' => optional('array'),
        'secrets' => optional('stacks_valid_secrets'),
        'certificates' => optional('stacks_valid_certificates'),
        'hooks' => optional('stacks_valid_hooks'),
        'only_if' => optional(-> (value) { value.is_a?(String) || value.is_a?(Hash) || value.is_a?(Array) }),
        'skip_if' => optional(-> (value) { value.is_a?(String) || value.is_a?(Hash) || value.is_a?(Array) }),
        'deploy' => optional({
          'strategy' => optional(%w(ha daemon random)),
          'wait_for_port' => optional('integer'),
          'min_health' => optional('float'),
          'interval' => optional(/^\d+(min|h|d|)$/)
        }),
        'health_check' => optional({
          'protocol' => /^(http|tcp)$/,
          'port' => 'integer',
          'uri' => optional(/\/[\S]*/),
          'timeout' => optional('integer'),
          'interval' => optional('integer'),
          'initial_delay' => optional('integer')
        }),
        'stop_signal' => optional('string'),
        'stop_grace_period' => optional(/(\d+(?:\.\d+)?)([hms])/),
        'read_only' => optional('boolean'),
        'entrypoint' => optional('string')
      }
    end

    def optional(type)
      HashValidator.optional(type)
    end

    def validate_options(service_config)
      HashValidator.validate(service_config, @schema, true)
    end

    def validate_volume_options(volume_config)
      HashValidator.validate(volume_config, volume_schema, true)
    end

    def validate_dependencies(dependency_config)
      HashValidator.validate(dependency_config, dependency_schema, true)
    end

    def volume_schema
      {
        'external' => optional(-> (value) { value.is_a?(TrueClass) || (value.is_a?(Hash) && value['name'].is_a?(String)) })
      }
    end

    def dependency_schema
      {
        'stack' => optional('string'),
        'variables' => optional(-> (value) { value.is_a?(Hash) })
      }
    end
  end
end
