##

# Copyright © 2016 by David Alger. All rights reserved
#
# Licensed under the Open Software License 3.0 (OSL-3.0)
# See included LICENSE file for full text of OSL-3.0
#
# http://davidalger.com/contact/
##

include Capistrano::Magento2::Helpers include Capistrano::Magento2::Setup

namespace :magento do

namespace :app do
  namespace :config do
    desc 'Create dump of application config'
    task :dump do
      on primary fetch(:magento_deploy_setup_role) do
        within release_path do
          execute :magento, 'app:config:dump'
        end
      end
    end

    desc 'Import data from shared config files'
    task :import do
      on primary fetch(:magento_deploy_setup_role) do
        within release_path do
          execute :magento, 'app:config:import --no-interaction'
        end
      end
    end

    desc 'Checks if config propagation requires update'
    task :status do
      on primary fetch(:magento_deploy_setup_role) do
        within release_path do
          execute :magento, 'app:config:status'
        end
      end
    end
  end
end

namespace :cache do
  desc 'Flush Magento cache storage'
  task :flush do
    on cache_hosts do
      within release_path do
        execute :magento, 'cache:flush'
      end
    end
  end

  desc 'Clean Magento cache by types'
  task :clean do
    on cache_hosts do
      within release_path do
        execute :magento, 'cache:clean'
      end
    end
  end

  desc 'Enable Magento cache'
  task :enable do
    on cache_hosts do
      within release_path do
        execute :magento, 'cache:enable'
      end
    end
  end

  desc 'Disable Magento cache'
  task :disable do
    on cache_hosts do
      within release_path do
        execute :magento, 'cache:disable'
      end
    end
  end

  desc 'Check Magento cache enabled status'
  task :status do
    on cache_hosts do
      within release_path do
        execute :magento, 'cache:status'
      end
    end
  end

  namespace :varnish do
    # TODO: Document what the magento:cache:varnish:ban task is for and how to use it. See also magento/magento2#4106
    desc 'Add ban to Varnish for url(s)'
    task :ban do
      on primary fetch(:magento_deploy_setup_role) do
        # TODO: Document use of :ban_pools and :varnish_cache_hosts in project config file
        next unless any? :ban_pools
        next unless any? :varnish_cache_hosts

        within release_path do
          for pool in fetch(:ban_pools) do
            for cache_host in fetch(:varnish_cache_hosts) do
              execute :curl, %W{-s -H 'X-Pool: #{pool}' -X PURGE #{cache_host}}
            end
          end
        end
      end
    end
  end
end

namespace :composer do
  desc 'Run composer install'
  task :install => :auth_config do

    on release_roles :all do
      within release_path do
        composer_flags = '--prefer-dist --no-interaction'

        if fetch(:magento_deploy_no_dev)
          composer_flags += ' --no-dev'
        end

        if fetch(:magento_deploy_production)
          composer_flags += ' --optimize-autoloader'
        end

        execute :composer, "install #{composer_flags} 2>&1"

        if test "[ -f #{release_path}/update/composer.json ]"   # can't count on this, but emit warning if not present
          execute :composer, "install #{composer_flags} -d ./update 2>&1"
        else
          puts "\e[0;31m    Warning: ./update/composer.json does not exist in repository!\n\e[0m\n"
        end
      end
    end
  end

  desc 'Run composer dump-autoload'
  task 'dump-autoload' do

    on release_roles :all do
      within release_path do
        composer_flags = '--no-interaction'

        if fetch(:magento_deploy_no_dev)
          composer_flags += ' --no-dev'
        end

        if fetch(:magento_deploy_production)
          composer_flags += ' --optimize'
        end

        execute :composer, "dump-autoload #{composer_flags} 2>&1"
      end
    end
  end

  task :auth_config do
    on release_roles :all do
      within release_path do
        if fetch(:magento_auth_public_key) and fetch(:magento_auth_private_key)
          execute :composer, :config, '-q',
            fetch(:magento_auth_repo_name),
            fetch(:magento_auth_public_key),
            fetch(:magento_auth_private_key),
            verbosity: Logger::DEBUG
        end
      end
    end
  end
end

namespace :deploy do
  namespace :mode do
    desc "Enables production mode"
    task :production do
      on release_roles(:all), in: :sequence, wait: 1 do
        within release_path do
          execute :magento, "deploy:mode:set production --skip-compilation"
        end
      end
    end

    desc "Displays current application mode"
    task :show do
      on release_roles :all do
        within release_path do
          execute :magento, "deploy:mode:show"
        end
      end
    end
  end

  task :check do
    on release_roles :all do
      next unless any? :linked_files_touch
      on release_roles :all do |host|
        join_paths(shared_path, fetch(:linked_files_touch)).each do |file|
          unless test "[ -f #{file} ]"
            execute :touch, file
          end
        end
      end
    end
  end

  task :verify do
    is_err = false
    on release_roles :all do
      unless test "[ -f #{release_path}/app/etc/config.php ]"
        error "\e[0;31mThe repository is missing app/etc/config.php. Please install the application and retry!\e[0m"
        exit 1  # only need to check the repo once, so we immediately exit
      end

      # Checking app/etc/env.php in shared_path vs release_path to support the zero-side-effect
      # builds as implemented in the :detect_scd_config hook of deploy.rake
      unless test %Q[#{SSHKit.config.command_map[:php]} -r '
            $cfg = include "#{shared_path}/app/etc/env.php";
            exit((int)!isset($cfg["install"]["date"]));
        ']
        error "\e[0;31mError on #{host}:\e[0m No environment configuration could be found." +
              " Please configure app/etc/env.php and retry!"
        is_err = true
      end
    end
    exit 1 if is_err
  end
end

namespace :setup do
  desc 'Updates the module load sequence and upgrades database schemas and data fixtures'
  task :upgrade do
    on primary fetch(:magento_deploy_setup_role) do
      within release_path do
        warn "\e[0;31mWarning: Use of magento:setup:upgrade on production systems is discouraged." +
             " See https://github.com/davidalger/capistrano-magento2/issues/34 for details.\e[0m\n"

        execute :magento, 'setup:upgrade --keep-generated'
      end
    end
  end

  namespace :db do
    desc 'Checks if DB schema or data requires upgrade'
    task :status do
      on primary fetch(:magento_deploy_setup_role) do
        within release_path do
          execute :magento, 'setup:db:status'
        end
      end
    end

    task :upgrade do
      on primary fetch(:magento_deploy_setup_role) do
        within release_path do
          db_status = capture :magento, 'setup:db:status --no-ansi', verbosity: Logger::INFO

          if not db_status.to_s.include? 'All modules are up to date'
            execute :magento, 'setup:db-schema:upgrade'
            execute :magento, 'setup:db-data:upgrade'
          end
        end
      end
    end

    desc 'Upgrades data fixtures'
    task 'schema:upgrade' do
      on primary fetch(:magento_deploy_setup_role) do
        within release_path do
          execute :magento, 'setup:db-schema:upgrade'
        end
      end
    end

    desc 'Upgrades database schema'
    task 'data:upgrade' do
      on primary fetch(:magento_deploy_setup_role) do
        within release_path do
          execute :magento, 'setup:db-data:upgrade'
        end
      end
    end
  end

  desc 'Sets proper permissions on application'
  task :permissions do
    on release_roles :all do
      within release_path do
        execute :find, release_path, "-type d ! -perm #{fetch(:magento_deploy_chmod_d).to_i} -exec chmod #{fetch(:magento_deploy_chmod_d).to_i} {} +"
        execute :find, release_path, "-type f ! -perm #{fetch(:magento_deploy_chmod_f).to_i} -exec chmod #{fetch(:magento_deploy_chmod_f).to_i} {} +"

        fetch(:magento_deploy_chmod_x).each() do |file|
          execute :chmod, "+x #{release_path}/#{file}"
        end
      end
    end
  end

  desc 'Sets proper selinux context on directories which are written to by web processes'
  task :selinux do
    on release_roles :all do
      with path: '/usr/sbin:$PATH' do
        if test "selinuxenabled"
          within release_path do
            fetch(:magento_deploy_chcon_dirs).each() do |dir|
              execute :chcon, "-RP -t #{fetch(:magento_deploy_chcon_type)} #{release_path}/#{dir}"
            end
          end
        end
      end
    end
  end

  namespace :di do
    desc 'Runs dependency injection compilation routine'
    task :compile do
      on release_roles :all do
        within release_path do
          with mage_mode: :production do
            execute :magento, "setup:di:compile"
          end
        end
      end
    end
  end

  namespace 'static-content' do
    desc 'Deploys static view files'
    task :deploy do
      on release_roles :all do
        with mage_mode: :production do
          deploy_languages = fetch(:magento_deploy_languages)
          if deploy_languages.count() > 0
            deploy_languages = deploy_languages.join(' ').prepend(' ')
          else
            deploy_languages = nil
          end

          deploy_themes = fetch(:magento_deploy_themes)
          if deploy_themes.count() > 0
            deploy_themes = deploy_themes.join(' -t ').prepend(' -t ')
          else
            deploy_themes = nil
          end

          deploy_jobs = fetch(:magento_deploy_jobs)
          if deploy_jobs
            deploy_jobs = " --jobs #{deploy_jobs}"
          else
            deploy_jobs = nil
          end

          # Static content compilation strategies that can be one of the following:
          # quick (default), standard (like previous versions) or compact
          compilation_strategy = fetch(:magento_deploy_strategy)
          if compilation_strategy
            compilation_strategy =  " -s #{compilation_strategy}"
          end

          within release_path do
            execute :magento, "setup:static-content:deploy#{compilation_strategy}#{deploy_jobs}#{deploy_languages}#{deploy_themes}"
          end

          # Set the deployed_version of static content to ensure it matches across all hosts
          upload!(StringIO.new(deployed_version), "#{release_path}/pub/static/deployed_version.txt")
        end
      end
    end
  end
end

namespace :maintenance do
  desc 'Enable maintenance mode'
  task :enable do
    on release_roles :all do
      within release_path do
        execute :magento, 'maintenance:enable'
      end
    end
  end

  # Internal command used to check if maintenance mode is neeeded and disable when zero-down deploy is
  # possible or when maintenance mode was previously enabled on the deploy target
  task :check do
    on primary fetch(:magento_deploy_setup_role) do
      maintenance_enabled = nil
      disable_maintenance = false     # Do not disable maintenance mode in absence of positive release checks

      if test "[ -d #{current_path} ]"
        within current_path do
          # If maintenance mode is already enabled, enable maintenance mode on new release and disable management to
          # avoid disabling maintenance mode in the event it was manually enabled prior to deployment
          info "Checking maintenance status..."
          maintenance_status = capture :magento, 'maintenance:status', raise_on_non_zero_exit: false

          if maintenance_status.to_s.include? 'maintenance mode is active'
            info "Maintenance mode is currently active."
            maintenance_enabled = true
          else
            info "Maintenance mode is currently inactive."
            maintenance_enabled = false
          end
          info ""
        end
      end

      # If maintenance is currently active, enable it on the newly deployed release
      if maintenance_enabled
        info "Enabling maintenance mode on new release to match active status of current release."
        on release_roles :all do
          within release_path do
            execute :magento, 'maintenance:enable'
          end
        end
        info ""
      end

      within release_path do
        info "Checking database status..."
        # Check setup:db:status output and if out-of-date do not disable maintenance mode
        database_status = capture :magento, 'setup:db:status', raise_on_non_zero_exit: false
        database_uptodate = false

        if database_status.to_s.include? 'All modules are up to date'
          info "All modules are up to date."
          info ""
          database_uptodate = true
        else
          puts "      #{database_status.gsub("\n", "\n      ").sub(" Run 'setup:upgrade' to update your DB schema and data.", "")}"
        end

        # Check app:config:status output and if out-of-date do not disable maintenance mode
        info "Checking config status..."
        config_status = capture :magento, 'app:config:status', raise_on_non_zero_exit: false
        config_uptodate = false

        if config_status.to_s.include? 'Config files are up to date'
          info "Config files are up to date."
          config_uptodate = true
        else
          puts "      #{config_status.gsub("\n", "\n      ").sub(" Run app:config:import or setup:upgrade command to synchronize configuration.", "")}"
        end
        info ""

        # If both checks above reported up-to-date status checks disable maintenance mode
        if database_uptodate and config_uptodate
          disable_maintenance = true
        end

        if maintenance_enabled
          info "Disabling maintenance mode management..."
          info "Maintenance mode was already active prior to deploy."
          set :magento_deploy_maintenance, false
        elsif disable_maintenance
          info "Disabling maintenance mode management..."
          info "There are no database updates or config changes. This is a zero-down deployment."
          set :magento_internal_zero_down_flag, true # Set internal flag to stop db schema/data upgrades from running
          set :magento_deploy_maintenance, false     # Disable maintenance mode management since it is not neccessary
        else
          info "Maintenance mode usage will be enforced per :magento_deploy_maintenance (setting is #{fetch(:magento_deploy_maintenance).to_s})"
        end
      end
    end
  end

  desc 'Disable maintenance mode'
  task :disable do
    on release_roles :all do
      within release_path do
        execute :magento, 'maintenance:disable'
      end
    end
  end

  desc 'Displays maintenance mode status'
  task :status do
    on release_roles :all do
      within release_path do
        execute :magento, 'maintenance:status'
      end
    end
  end

  desc 'Sets maintenance mode exempt IPs'
  task 'allow-ips', :ip do |t, args|
    on release_roles :all do
      within release_path do
        execute :magento, 'maintenance:allow-ips', args[:ip]
      end
    end
  end
end

namespace :indexer do
  desc 'Reindex data by all indexers'
  task :reindex do
    on primary fetch(:magento_deploy_setup_role) do
      within release_path do
        execute :magento, 'indexer:reindex'
      end
    end
  end

  desc 'Shows allowed indexers'
  task :info do
    on primary fetch(:magento_deploy_setup_role) do
      within release_path do
        execute :magento, 'indexer:info'
      end
    end
  end

  desc 'Shows status of all indexers'
  task :status do
    on primary fetch(:magento_deploy_setup_role) do
      within release_path do
        execute :magento, 'indexer:status'
      end
    end
  end

  desc 'Shows mode of all indexers'
  task 'show-mode', :index do |t, args|
    on primary fetch(:magento_deploy_setup_role) do
      within release_path do
        execute :magento, 'indexer:show-mode', args[:index]
      end
    end
  end

  desc 'Sets mode of all indexers'
  task 'set-mode', :mode, :index do |t, args|
    on primary fetch(:magento_deploy_setup_role) do
      within release_path do
        execute :magento, 'indexer:set-mode', args[:mode], args[:index]
      end
    end
  end
end

end