require 'fugit'

module Agents
  class SchedulerAgent < Agent
    include AgentControllerConcern

    cannot_be_scheduled!
    cannot_receive_events!
    cannot_create_events!

    @@second_precision_enabled = ENV['ENABLE_SECOND_PRECISION_SCHEDULE'] == 'true'

    cattr_reader :second_precision_enabled

    description <<~MD
      The Scheduler Agent periodically takes an action on target Agents according to a user-defined schedule.

      # Action types

      Set `action` to one of the action types below:

      * `run`: Target Agents are run at intervals, except for those disabled.

      * `disable`: Target Agents are disabled (if not) at intervals.

      * `enable`: Target Agents are enabled (if not) at intervals.
        * If the option `drop_pending_events` is set to `true`, pending events will be cleared before the agent is enabled.

      # Targets

      Select Agents that you want to run periodically by this SchedulerAgent.

      # Schedule

      Set `schedule` to a schedule specification in the [cron](http://en.wikipedia.org/wiki/Cron) format.
      For example:

      * `0 22 * * 1-5`: every day of the week at 22:00 (10pm)

      * `*/10 8-11 * * *`: every 10 minutes from 8:00 to and not including 12:00

      This variant has several extensions as explained below.

      ## Timezones

      You can optionally specify a timezone (default: `#{Time.zone.name}`) after the day-of-week field using the labels in the [tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)

      * `0 22 * * 1-5 Europe/Paris`: every day of the week when it's 22:00 in Paris

      * `0 22 * * 1-5 Etc/GMT+2`: every day of the week when it's 22:00 in GMT+2

      ## Seconds

      You can optionally specify seconds before the minute field.

      * `*/30 * * * * *`: every 30 seconds

      #{"Only multiples of fifteen are allowed as values for the seconds field, i.e. `*/15`, `*/30`, `15,45` etc." unless second_precision_enabled}

      ## Last day of month

      `L` signifies "last day of month" in `day-of-month`.

      * `0 22 L * *`: every month on the last day at 22:00

      ## Weekday names

      You can use three letter names instead of numbers in the `weekdays` field.

      * `0 22 * * Sat,Sun`: every Saturday and Sunday, at 22:00

      ## Nth weekday of the month

      You can specify "nth weekday of the month" like this.

      * `0 22 * * Sun#1,Sun#2`: every first and second Sunday of the month, at 22:00

      * `0 22 * * Sun#L1`: every last Sunday of the month, at 22:00
    MD

    def default_options
      super.update({
        'schedule' => '0 * * * *',
      })
    end

    def working?
      true
    end

    def validate_options
      if (spec = options['schedule']).present?
        begin
          cron = Fugit::Cron.new(spec) or raise ArgumentError
          unless second_precision_enabled || (cron.seconds - [0, 15, 30, 45, 60]).empty?
            errors.add(:base, "second precision schedule is not allowed in this service")
          end
        rescue ArgumentError
          errors.add(:base, "invalid schedule")
        end
      else
        errors.add(:base, "schedule is missing")
      end
    end

    before_save do
      self.memory.delete('scheduled_at') if self.options_changed?
    end
  end
end
