# frozen_string_literal: true

class UxToolkitRepositoryRefreshState < ActiveRecord::Base
  LOCK_TIMEOUT = 30.minutes
  MESSAGE_LIMIT = 1000
  STATUS_RUNNING = 'running'
  STATUS_SUCCESS = 'success'
  STATUS_FAILED = 'failed'

  belongs_to :repository, optional: true
  belongs_to :project, optional: true

  validates :repository_id, presence: true, uniqueness: true
  validates :refresh_interval_minutes, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: -1}
  validates :consecutive_failure_count, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 0}

  scope :enabled, -> { where(enabled: true) }
  scope :due, lambda {
    where('refresh_interval_minutes <> 0')
      .where('next_run_at IS NULL OR next_run_at <= ?', Time.current)
  }
  scope :unlocked, -> { where('locked_until IS NULL OR locked_until <= ?', Time.current) }

  def self.table_available?
    ActiveRecord::Base.connection.data_source_exists?('ux_toolkit_repository_refresh_states')
  rescue ActiveRecord::ActiveRecordError
    false
  end

  def self.state_for(repository)
    find_or_initialize_by(repository_id: repository.id).tap do |state|
      state.project_id ||= repository.project_id
      state.refresh_interval_minutes = RedmineUxToolkit::Services::RepositoryRefresh::INHERIT_INTERVAL_MINUTES if state.new_record?
    end
  end

  def self.save_all_from_params(rows)
    return unless table_available?

    Array(rows).each do |_key, row|
      repository_id = row[:repository_id].presence || row['repository_id'].presence
      repository = Repository.find_by(id: repository_id)
      next unless repository

      state = state_for(repository)
      state.project_id = repository.project_id
      state.enabled = enabled_param?(row)
      state.refresh_interval_minutes = sanitized_interval(row)
      state.next_run_at ||= Time.current if state.enabled? && state.effective_refresh_interval_minutes.positive?
      state.save!
    end
  end

  def uses_default_refresh_interval?
    refresh_interval_minutes.to_i == RedmineUxToolkit::Services::RepositoryRefresh::INHERIT_INTERVAL_MINUTES
  end

  def effective_refresh_interval_minutes(settings = Setting.plugin_redmine_ux_toolkit || {})
    return RedmineUxToolkit::Services::RepositoryRefresh.default_interval_minutes(settings) if uses_default_refresh_interval?

    refresh_interval_minutes.to_i
  end

  def claim!(lock_timeout: LOCK_TIMEOUT)
    now = Time.current
    updated_rows = self.class.where(id: id)
                             .where('locked_until IS NULL OR locked_until <= ?', now)
                             .update_all(
                               last_status: STATUS_RUNNING,
                               last_message: I18n.t(:text_ux_toolkit_repository_refresh_running),
                               locked_until: now + lock_timeout,
                               updated_at: now
                             )

    updated_rows == 1 ? reload : nil
  end

  def release_success!(message)
    now = Time.current
    update!(
      last_run_at: now,
      next_run_at: next_success_run_at(now),
      last_status: STATUS_SUCCESS,
      last_message: truncate_message(message),
      consecutive_failure_count: 0,
      locked_until: nil
    )
  end

  def release_failure!(message, backoff_enabled:)
    now = Time.current
    failures = consecutive_failure_count.to_i + 1
    update!(
      last_run_at: now,
      next_run_at: next_failure_run_at(now, failures, backoff_enabled),
      last_status: STATUS_FAILED,
      last_message: truncate_message(message),
      consecutive_failure_count: failures,
      locked_until: nil
    )
  end

  def repository_label
    repo = repository
    return "Repository ##{repository_id}" unless repo

    project_name = repo.project ? repo.project.name : "Project ##{repo.project_id}"
    "#{project_name} - #{repo.name}"
  end

  private

  def self.enabled_param?(row)
    (row[:enabled] || row['enabled']).to_s == '1'
  end

  def self.sanitized_interval(row)
    value = (row[:refresh_interval_minutes] || row['refresh_interval_minutes']).to_i
    if RedmineUxToolkit::Services::RepositoryRefresh.allowed_repository_interval?(value)
      value
    else
      RedmineUxToolkit::Services::RepositoryRefresh::INHERIT_INTERVAL_MINUTES
    end
  end

  def next_success_run_at(now)
    interval = effective_refresh_interval_minutes
    interval.positive? ? now + interval.minutes : nil
  end

  def next_failure_run_at(now, failures, backoff_enabled)
    return next_success_run_at(now) unless backoff_enabled

    delay_minutes = [effective_refresh_interval_minutes, 30].max * failures
    now + [delay_minutes, 24.hours / 60].min.minutes
  end

  def truncate_message(message)
    message.to_s.truncate(MESSAGE_LIMIT)
  end
end
