require "ops_manager/api/opsman"
require "ops_manager/api/pivnet"
require 'ops_manager/config/opsman_deployment'
require 'fileutils'

class OpsManager::ApplianceDeployment
  extend Forwardable
  attr_reader :config

  def_delegators :pivnet_api, :get_product_releases, :accept_product_release_eula,
    :get_product_release_files, :download_product_release_file
  def_delegators :opsman_api, :create_user, :get_installation_assets,
    :get_installation_settings, :get_diagnostic_report, :upload_installation_assets, :get_ensure_availability,
    :import_stemcell, :target, :password, :username, :ops_manager_version= , :reset_access_token, :get_pending_changes,
    :wait_for_https_alive

  attr_reader :config_file

  def initialize(config_file)
    @config_file = config_file
  end

  def run
    OpsManager.set_conf(:target, config[:ip])
    OpsManager.set_conf(:username, config[:username])
    OpsManager.set_conf(:password, config[:password])
    OpsManager.set_conf(:pivnet_token, config[:pivnet_token])


    case
    when current_version.empty?
      puts "No OpsManager deployed at #{config[:ip]}. Deploying ...".green
      deploy
      create_first_user
    when current_version < desired_version then
      puts "OpsManager at #{config[:ip]} version is #{current_version}. Upgrading to #{desired_version} .../".green
      upgrade
    when current_version == desired_version then
      if pending_changes?
        puts "OpsManager at #{config[:ip]} version has pending changes. Applying changes...".green
        products = "all"
        if config[:selected_deployments]
          products = config[:selected_deployments]
        elsif config[:single_tile_deploy]
          products = "none"
        end
        OpsManager::InstallationRunner.trigger!(products).wait_for_result
      else
        puts "OpsManager at #{config[:ip]} version is already #{config[:desired_version]}. Skiping ...".green
      end
    end

    puts '====> Finish!'.green
  end

  def appliance 
    @appliance ||= if config[:provider] =~/vsphere/i
      OpsManager::Appliance::Vsphere.new(config)
    else
      OpsManager::Appliance::AWS.new(config)
    end
  end

  def create_first_user
    puts '====> Creating initial user'.green
    until( create_user.code.to_i == 200) do
      print ' .'.green ; sleep 1
    end
  end

  def deploy
    appliance.deploy_vm
    wait_for_https_alive 300
  end

  def upgrade
    get_installation_assets
    download_current_stemcells
    appliance.stop_current_vm(current_name)
    deploy
    upload_installation_assets
    wait_for_uaa
    provision_stemcells
    products = "all"
    if config[:selected_deployments]
      products = config[:selected_deployments]
    elsif config[:single_tile_deploy]
      products = "none"
    end
    OpsManager::InstallationRunner.trigger!(products).wait_for_result
  end

  def list_current_stemcells
    JSON.parse(installation_settings).fetch('products').inject([]) do |a, p|
      product_name = "stemcells"
      if p['stemcell'].fetch('os') =~ /windows/i
        product_name = "stemcells-windows-server"
      end
      if p['stemcell'].fetch('os') =~ /ubuntu-xenial/i
        product_name = "stemcells-ubuntu-xenial"
      end
      a << { version: p['stemcell'].fetch('version'), product: product_name }
    end.uniq
  end

  # Finds available stemcell's pivotal network release.
  # If it can not find the exact version it will try to find the newest minor version available.
  # #
  # @param version [String] the version number, eg: '2362.17'
  # @return release_id [Integer] the pivotal netowkr release id of the found stemcell.
  def find_stemcell_release(version, product_name)
    version  = OpsManager::Semver.new(version)
    releases = stemcell_releases(product_name).collect do |r|
      {
        release_id:  r['id'],
        version:     OpsManager::Semver.new(r['version']),
      }
    end
    releases.keep_if{ |r| r[:version].major == version.major }
    exact_version = releases.select {|r| r[:version] == version }
    return exact_version.first[:release_id] unless exact_version.empty?
    releases_sorted_by_version = releases.sort_by{ |r| r[:version].minor }.reverse
    return releases_sorted_by_version.first[:release_id] unless releases_sorted_by_version.empty?
  end

  # Finds stemcell's pivotal network release file.
  # #
  # @param release_id [String] the version number, eg: '2362.17'
  # @param filename [Regex] the version number, eg: /vsphere/
  # @return id and name [Array] the pivotal network file ID and Filename for the matching stemcell.
  def find_stemcell_file(release_id, filename, product_name)
    files = JSON.parse(get_product_release_files(product_name, release_id).body).fetch('product_files')
    file = files.select{ |r| r.fetch('aws_object_key') =~ filename }.first
    return file['id'], file['aws_object_key'].split('/')[-1]
  end

  # Lists all the available stemcells in the current installation_settings.
  # Downloads those stemcells.
  def download_current_stemcells
    print "====> Downloading existing stemcells ...".green
    puts "no stemcells found".green if list_current_stemcells.empty?
    FileUtils.mkdir_p current_stemcell_dir
    list_current_stemcells.each do |stemcell_info|
      stemcell_version = stemcell_info[:version]
      product_name = stemcell_info[:product]
      release_id = find_stemcell_release(stemcell_version, product_name)
      accept_product_release_eula(product_name, release_id)
      stemcell_regex = /vsphere/
      if config[:provider] == "AWS"
        stemcell_regex = /aws/
      end

      file_id, file_name = find_stemcell_file(release_id, stemcell_regex, product_name)
      download_product_release_file(product_name, release_id, file_id, write_to: "#{current_stemcell_dir}/#{file_name}")
    end
  end

  def new_vm_name
    @new_vm_name ||= "#{config[:name]}-#{config[:desired_version]}"
  end

  def current_version
    @current_version ||= OpsManager::Semver.new(version_from_diagnostic_report)
  end

  def current_name
    @current_name ||= "#{config[:name]}-#{current_version}"
  end

  def desired_version
    @desired_version ||= OpsManager::Semver.new(config[:desired_version])
  end

  def provision_stemcells
    reset_access_token
    Dir.glob("#{current_stemcell_dir}/*").each do |stemcell_filepath|
      import_stemcell(stemcell_filepath)
    end
  end

  def wait_for_uaa
    puts '====> Waiting for UAA to become available ...'.green
    while !uaa_available?
      sleep(5)
    end
  end

  private
  def uaa_available?
    res = get_ensure_availability
    res.code.eql? '302' and res.body.include? '/auth/cloudfoundry'
  end

  def diagnostic_report
    @diagnostic_report ||= get_diagnostic_report
  end

  def version_from_diagnostic_report
    return unless diagnostic_report
    version = parsed_diagnostic_report
      .fetch("versions")
      .fetch("release_version")
    version.gsub(/.0$/,'')
  end

  def parsed_diagnostic_report
    JSON.parse(diagnostic_report.body)
  end

  def current_vm_name
    @current_vm_name ||= "#{config[:name]}-#{current_version}"
  end


  def pivnet_api
    @pivnet_api ||= OpsManager::Api::Pivnet.new
  end

  def opsman_api
    @opsman_api ||= OpsManager::Api::Opsman.new
  end

  def config
    @config ||= OpsManager::Config::OpsmanDeployment.new(YAML.load_file(@config_file))
  end


  def desired_version?(version)
    !!(desired_version.to_s =~/#{version}/)
  end

  def installation_settings
    @installation_settings ||= get_installation_settings.body
  end

  def stemcell_releases(product_name)
    JSON.parse(get_product_releases(product_name).body).fetch('releases')
  end

  def current_stemcell_dir
    "/tmp/current_stemcells"
  end

  def pending_changes?
    !JSON.parse(get_pending_changes.body).fetch('product_changes').empty?
  end
end
