require 'rake/clean'
require 'rake/packagetask'
require 'bundler'

# We setup our configuration to create a fake "installation" to this directory.
# In cases you run everything locally, it dumps data, pid, and log files under
# here.
#
# See tasks like basic:start or cloud:start for real usage.
INSTALL_DIR = '/tmp/hws'
CLEAN.include(INSTALL_DIR)

task :set_writable do
  Dir.glob('**/Gemfile.lock').each do |f|
    File.chmod(0644, f)
  end
end

Rake::PackageTask.new('helix-web-services', :noversion) do |package|
  package.need_tar = true

  package.package_files.include('data/certs/*')
  package.package_files.include('contrib/**/*')
  package.package_files.include('doc/**/*')
  package.package_files.include('git_fusion_strings/**/*')
  package.package_files.include('helix_web_services/**/*')
  package.package_files.include('helix_web_services_client/**/*')
  package.package_files.include('packaging/**/*')
  package.package_files.include('Gemfile')
  package.package_files.include('Gemfile.lock')
  package.package_files.include('Rakefile')
  package.package_files.include('LICENSE')
  package.package_files.include('helix-web-services-notes.txt')
end
CLEAN.include('pkg')

task :package => [:set_writable]

desc 'Create primary source deliverables'
task :build => [:package]

desc 'Rebuild the main ASCIIDoc documentation'
task :asciidoc do
  system('bundle exec asciidoctor -o doc-output/p4ws.html doc/p4ws.asc') ||
      fail('asciidoctor failed')
end
CLEAN.include('**/doc-output')

namespace :all do
  desc 'Rebuild documentation across system'
  task :doc => [:asciidoc] do
    system('cd helix_web_services_client && bundle exec rake yard') ||
        fail('rake yard failed for helix_web_services_client')
    system('cd helix_web_services && bundle exec rake yard') ||
        fail('rake yard failed for helix_web_services')
  end
end

# "Raymond" is an internal server API used in Helix Cloud, that Helix Web
# Services integration uses for Helix Sync methods. This mock instance lets
# us run tests in our "cloud" configuration setup.
namespace :mock_raymond do
  desc 'start mock cloud server'
  task :start do
    start_mock_raymond
  end

  desc 'kill mock cloud server'
  task :stop do
    stop_mock_raymond
  end
end

namespace :p4d do
  namespace :basic do
    desc 'init p4d in basic mode'
    task :start do
      initialize_p4d('data/p4init')
    end

  end

  namespace :cloud do
    desc 'init p4d in cloud mode'
    task :start do
      initialize_p4d('data/cloudinit')
    end
  end

  desc 'halt running p4d'
  task :stop do
    stop_p4d
  end
end


# The 'basic' configuration does not include Helix Cloud or Git Fusion
#
# Note: Git Fusion only runs on Linux, so that's left out of the dev environment
# for now, until I decide to switch my dev environment to run on Linux. :)
namespace :basic do
  desc 'Start nginx, p4d, unicorn with basic configuration'
  task :start do |t|
    err = initialize_tmp_install
    err ||= initialize_p4d('data/p4init')
    err ||= hws_launch_tmp_install('start')

    fail('start failed') if err
  end

  desc 'Run helix_web_services and helix_web_services_client specs against the local basic config'
  task :spec do
    err = run_hws_server_tests(spec_output: 'basic_helix_web_services_specs.html')
    err ||= run_hws_client_specs(spec_output: 'basic_helix_web_services_client_specs.html',
                                 p4port: 'localhost:1666')

    fail('spec failed') if err
  end

  desc 'Stop and clean up the basic configuration'
  task :stop do
    err ||= hws_launch_tmp_install('stop')
    err ||= stop_p4d

    fail('stop failed') if err
  end
end

namespace :cloud do
  desc 'Start mock_raymond, nginx, unicorn, and p4d for Helix Cloud mock testing'
  task :start => 'mock_raymond:start' do
    err = initialize_tmp_install
    err ||= initialize_p4d('data/cloudinit')
    err ||= hws_launch_tmp_install('start',
                                   cloud_settings: 'helix_web_services/config/hws_cloud_settings.conf.example')
  end

  desc 'Run helix_web_services_client specs against the local basic config'
  task :spec do
    err = run_hws_server_tests(cloud_settings: 'helix_web_services/config/hws_cloud_settings.conf.example',
                               spec_output: 'cloud_helix_web_services_specs.html')
    err ||= run_hws_client_specs(spec_output: 'cloud_helix_web_services_client_specs.html',
                                 p4port: 'localhost:1666',
                                 cloud_test: true)

    fail('spec failed') if err
  end

  desc 'Shut down Helix Cloud setup'
  task :stop => 'mock_raymond:stop' do
    err ||= hws_launch_tmp_install('stop')
    err ||= stop_p4d

    fail('stop failed') if err
  end
end

namespace :remote do
  desc 'Exec specs against a remote HWS instance'
  task :spec, [:hws_url, :p4port] do |t, args|
    hws_url = args[:host]
    p4port = args[:p4port]

    err = run_hws_client_specs(spec_output: "remote_#{host}_helix_web_services_client_specs.html",
                               p4port: p4port,
                               hws_url: hws_url)

    fail('spec failed') if err
  end
end

#=============================================================================
# Helper Methods
#=============================================================================

# Execute helix_web_services_client specs, with different configurations:
#
# - cloud_test: if true, we assume the mock_raymond server is running
#
# - spec_output: specify different names for different configurations, output
#                will go under ./spec-output
#
# - p4port: Some tests require seeding test data directly, indicate the
#           p4d port, defaults to 'localhost:1666'
#
# - hws_url: The base URL to the HWS server, defaults "https://localhost:9000/"
def run_hws_client_specs(cloud_test: false,
                         spec_output: 'helix_web_services_client_specs.html',
                         p4port: 'localhost:1666',
                         hws_url: 'https://localhost:9000/')

  output_file = File.absolute_path("../spec-output/#{spec_output}", __FILE__)

  ok = system('cd helix_web_services_client && ' +
                  "CLOUD_TEST=#{cloud_test.to_s} " +
                  "SPEC_OUT=#{output_file} " +
                  "P4PORT=#{p4port} " +
                  "HWS_URL=#{hws_url} " +
                  'bundle exec rake spec')

  return true unless ok
end

# Execute helix_web_services specs, with different configurations.
#
# Note: This typically does *not* require unicorn and nginx to run, just p4d.
# It is a different configuration that *can* be easier for debugging purposes.
#
# - cloud_settings: Path to the cloud settings file if you want to validate
#                   Helix Cloud logic
#
# - spec_output: Test result output file name.
def run_hws_server_tests(cloud_settings: '', spec_output: '')

  output_file = File.absolute_path("../spec-output/#{spec_output}", __FILE__)

  settings_file = File.absolute_path("../#{cloud_settings}", __FILE__) if !cloud_settings.empty?

  ok = system('cd helix_web_services && ' +
                  "CLOUD_SETTINGS=#{settings_file} " +
                  "SPEC_OUT=#{output_file} " +
                  'bundle exec rake spec')

  return true unless ok
end


# Create the temporary install configuration file that points to files in
# a temporary directory, /tmp/hws.
#
# It is very annoying in development if this becomes a long-winded path.
def initialize_tmp_install
  unless Dir.exist?(INSTALL_DIR)
    FileUtils.mkdir_p(INSTALL_DIR)
  end
  unless Dir.exist?(data_dir)
    FileUtils.mkdir_p(data_dir)
  end
  unless Dir.exist?(log_dir)
    FileUtils.mkdir_p(log_dir)
  end
  unless Dir.exist?(run_dir)
    FileUtils.mkdir_p(run_dir)
  end
  create_dev_config_file
end

# Runs hws_launch in temp install directory.
def hws_launch_tmp_install(cmd, cloud_settings: nil)
  puts "hws_launch #{cmd}"

  if cloud_settings.nil?
    cloud_settings = ''
  else
    cloud_settings = "CLOUD_SETTINGS=#{File.absolute_path("../#{cloud_settings}", __FILE__)} "
  end

  launch_ok = system('cd helix_web_services && ' +
                         "HWS_CONFIG=#{hws_config_path} " +
                         cloud_settings +
                         "bundle exec #{hws_launch_path} #{cmd}")
  !launch_ok
end

# This generates /tmp/helix-web-services.conf that points to locations in the
# local directory... as appropriate.
def create_dev_config_file(enable_https: false)
  IO.write(hws_config_path, <<-END.gsub(/^[ ]{4}/, '')
    DATA_DIR: '#{data_dir}'
    ENABLE_HTTPS: #{enable_https}
    LOG_DIR: '#{log_dir}'
    NGINX_COMMAND: '#{nginx_command}'
    NGINX_CONFIG_PATH: '#{nginx_config_path}'
    NGINX_PORT: 9000
    P4TRUST: '#{p4trust_path}'
    RACKUP_CONFIG: '#{rackup_config_path}'
    RUN_DIR: '#{run_dir}'
    SSL_CERTIFICATE_PATH: '#{ssl_certificate_path}'
    SSL_CERTIFICATE_KEY_PATH: '#{ssl_certificate_key_path}'
    SYSTEM_GROUP: null
    SYSTEM_USER: '#{ENV['USER']}'
    WORKSPACE_DIR: '#{workspace_dir}'
    UNICORN_COMMAND: '#{unicorn_command}'
    UNICORN_CONFIG_PATH: '#{unicorn_config_path}'
    UNICORN_CONNECTION: '#{unicorn_socket_path}'
  END
  )
  nil
end

def initialize_p4d(data_dir)
  puts 'starting p4d from ' + data_dir

  ok = system('p4util kill')
  return true unless ok

  if Dir.exist?('/tmp/p4util/p4droot')
    require 'fileutils'
    FileUtils.rmtree('/tmp/p4util/p4droot')
  end

  ok = system('p4util start')
  return true unless ok

  ok = system("p4util init #{data_dir}")
  return true unless ok
end

def stop_p4d
  ok = system('p4util kill')
  return true unless ok

  if Dir.exist?('/tmp/p4util/p4droot')
    require 'fileutils'
    FileUtils.rmtree('/tmp/p4util/p4droot')
  end

  nil
end

def initialize_remote_p4d(host, data_dir)
  ok = system("p4util init -p #{host}:1666 -a #{data_dir}")
  return true unless ok
end

def start_mock_raymond(cloud_settings: '')
  # return
  puts 'starting mock_raymond'
  begin
    Bundler.with_clean_env do
      ok = system(
          'cd helix_web_services/mock_raymond && ' +
              'UNICORN_PID=/tmp/mock_raymond_unicorn.pid ' +
              'RAILS_ENV=test ' +
              'MOCKRAYMOND_STDOUT_PATH=/tmp/mock_raymond_unicorn.out ' +
              'MOCKRAYMOND_STDERR_PATH=/tmp/mock_raymond_unicorn.err ' +
              'P4PORT=localhost:1666 ' +
              'P4CHARSET=utf8 ' +
              'bundle exec unicorn -c config/unicorn.rb -D'
      )
      return true unless ok

      while connect_to_server(3000) == false
        sleep(0.1)
      end
    end

    return false
  rescue Exception => e
    puts "Error: #{e.message}"
    return true
  end
end

def stop_mock_raymond
  if File.exist?('/tmp/mock_raymond_unicorn.pid')
    rails_pid = IO.read('/tmp/mock_raymond_unicorn.pid').to_i
    puts "killing pid: #{rails_pid}"
    Process.kill('TERM', rails_pid)
    File.unlink('/tmp/mock_raymond_unicorn.pid')
  end
end

def hws_config_path
  "#{INSTALL_DIR}/helix-web-services.conf"
end

# In development mode, we use the rackup configuration directly in the tree
def rackup_config_path
  File.absolute_path('../helix_web_services/config.ru', __FILE__)
end

# Note: if you're not running within bundler, or RVM with fancy bundler
# shell integration, this is possibly going to fail
def hws_launch_path
  File.absolute_path('../helix_web_services/bin/hws_launch', __FILE__)
end

# In general, unicorn should be installed already, and in most dev environments
# is in the path via RVM.
def unicorn_command
  unicorn_path = `which unicorn`.strip
  fail 'unicorn not found' if unicorn_path.empty?
  unicorn_path
end

def nginx_command
  nginx_path = `which nginx`.strip
  fail 'nginx not found' if nginx_path.empty?
  nginx_path
end

def nginx_config_path
  "#{INSTALL_DIR}/nginx.conf"
end

def unicorn_config_path
  "#{INSTALL_DIR}/unicorn.conf"
end

def data_dir
  "#{INSTALL_DIR}/data"
end

def log_dir
  "#{INSTALL_DIR}/logs"
end

def run_dir
  "#{INSTALL_DIR}/run"
end

def unicorn_socket_path
  "unix:#{run_dir}/unicorn.sock"
end

def workspace_dir
  "#{data_dir}/workspaces"
end

def ssl_certificate_path
  File.absolute_path('../data/certs/nginx.crt', __FILE__)
end

def ssl_certificate_key_path
  File.absolute_path('../data/certs/nginx.key', __FILE__)
end

def p4trust_path
  "#{INSTALL_DIR}/p4trust"
end

def connect_to_server(port)
  require 'socket'
  begin
    s = TCPSocket.new 'localhost', port
    s.close
    return true
  rescue Exception => e
    return false
  end
end