# frozen_string_literal: true

require 'open3'
require 'rbconfig'

module RedmineUxToolkit
  module Services
    class HelpdeskMailFetch
      PLUGIN_ID = :redmine_contacts_helpdesk
      STATE_NAME = 'helpdesk_mail_fetch'
      DEFAULT_INTERVAL_MINUTES = 60
      EVERY_CRON_RUN = 0
      ALLOWED_INTERVAL_MINUTES = [EVERY_CRON_RUN, 30, 60, 120, 240, 480].freeze
      LOCK_TIMEOUT = 30.minutes
      MESSAGE_LIMIT = 1000
      RAKE_TASK = 'redmine:email:helpdesk:receive'
      HELPDESK_ERROR_PATTERN = /Helpdesk MailHandler: can't get mail/i

      Result = Struct.new(:status, :message, keyword_init: true)

      class << self
        def available?
          Redmine::Plugin.installed?(PLUGIN_ID)
        rescue StandardError
          false
        end

        def interval_minutes(settings = Setting.plugin_redmine_ux_toolkit || {})
          raw_value = settings['helpdesk_mail_fetch_interval_minutes']
          return DEFAULT_INTERVAL_MINUTES unless raw_value.to_s.match?(/\A\d+\z/)

          value = raw_value.to_i
          ALLOWED_INTERVAL_MINUTES.include?(value) ? value : DEFAULT_INTERVAL_MINUTES
        end

        def due?(state: state_record, now: Time.current, settings: Setting.plugin_redmine_ux_toolkit || {})
          return false unless available? && table_available?

          interval = interval_minutes(settings)
          return true if interval == EVERY_CRON_RUN || state&.last_finished_at.nil?

          state.last_finished_at <= now - interval.minutes
        end

        def run_due(logger: Rails.logger)
          return skipped(I18n.t(:text_ux_toolkit_helpdesk_plugin_not_installed)) unless available?
          return skipped(I18n.t(:text_ux_toolkit_helpdesk_mail_fetch_status_unavailable)) unless table_available?

          state = state_record
          return skipped(I18n.t(:text_ux_toolkit_helpdesk_mail_fetch_not_due)) unless due?(state: state)
          return locked unless claim!(state)

          run_mail_fetch(state, logger)
        end

        def table_available?
          ActiveRecord::Base.connection.data_source_exists?('ux_toolkit_cron_states')
        rescue ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid
          false
        end

        def state_record
          return nil unless table_available?

          UxToolkitCronState.find_or_create_by!(name: STATE_NAME)
        rescue ActiveRecord::RecordNotUnique
          UxToolkitCronState.find_by!(name: STATE_NAME)
        end

        private

        def run_mail_fetch(state, logger)
          stdout, stderr, process_status = Open3.capture3(
            {'RAILS_ENV' => Rails.env.to_s},
            RbConfig.ruby,
            bundle_executable,
            'exec',
            'rake',
            RAKE_TASK,
            chdir: Rails.root.to_s
          )

          output = [stdout, stderr].reject(&:blank?).join("\n")
          if !process_status.success? || output.match?(HELPDESK_ERROR_PATTERN)
            logger&.error("[Redmine UX Toolkit] Helpdesk Rake task failed: #{output.to_s.truncate(MESSAGE_LIMIT)}")
            message = I18n.t(:error_ux_toolkit_helpdesk_mail_fetch_command_failed)
            release!(state, status: 'failed', message: message)
            return Result.new(status: 'failed', message: message)
          end

          message = I18n.t(:notice_ux_toolkit_helpdesk_mail_fetch_succeeded)
          release!(state, status: 'success', message: message, successful_at: Time.current)
          Result.new(status: 'success', message: message)
        rescue StandardError => e
          logger&.error("[Redmine UX Toolkit] Helpdesk mail fetch failed: #{e.class}: #{e.message}")
          release!(state, status: 'failed', message: "#{e.class}: #{e.message}")
          Result.new(status: 'failed', message: e.message)
        end

        def bundle_executable
          Gem.bin_path('bundler', 'bundle')
        end

        def claim!(state)
          now = Time.current
          UxToolkitCronState
            .where(id: state.id)
            .where('locked_until IS NULL OR locked_until <= ?', now)
            .update_all(
              last_started_at: now,
              last_status: 'running',
              last_message: I18n.t(:text_ux_toolkit_helpdesk_mail_fetch_started),
              locked_until: now + LOCK_TIMEOUT,
              updated_at: now
            ) == 1
        end

        # For this named state, last_finished_at intentionally means the last
        # successful completion so failed retries do not erase the heartbeat.
        def release!(state, status:, message:, successful_at: nil)
          attributes = {
            last_status: status,
            last_message: message.to_s.truncate(MESSAGE_LIMIT),
            locked_until: nil,
            updated_at: Time.current
          }
          attributes[:last_finished_at] = successful_at if successful_at
          state.update_columns(attributes)
        rescue StandardError => e
          Rails.logger&.error("[Redmine UX Toolkit] Could not persist Helpdesk mail fetch state: #{e.class}: #{e.message}")
        end

        def locked
          Result.new(
            status: 'locked',
            message: I18n.t(:error_ux_toolkit_helpdesk_mail_fetch_already_running)
          )
        end

        def skipped(message)
          Result.new(status: 'skipped', message: message)
        end
      end
    end
  end
end
