##
 # 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
