# frozen_string_literal: true

require 'digest'
require 'mail'
require 'uri'

module RedmineUxToolkit
  module Services
    class CronMonitor
      INTERVAL_NONE = 'none'
      INTERVAL_FOUR_HOURS = '4_hours'
      ALLOWED_INTERVALS = [INTERVAL_NONE, INTERVAL_FOUR_HOURS].freeze
      FOUR_HOURS = 4.hours
      CACHE_KEY = 'redmine_ux_toolkit/cron_monitor/check'
      CACHE_TTL = 1.minute

      class << self
        def check_from_web_request(server_admin_email: nil, now: Time.current, logger: Rails.logger)
          settings = Setting.plugin_redmine_ux_toolkit || {}
          return unless enabled?(settings)
          recipient = monitoring_recipient(settings, server_admin_email: server_admin_email)
          return unless recipient
          return unless table_available?
          return unless cache_gate_open?

          monitor_state = UxToolkitCronMonitorState.claim_check!(now: now)
          return unless monitor_state

          inspect_runner(monitor_state, recipient, now, logger)
        rescue StandardError => e
          logger&.error("[Redmine UX Toolkit] Cron monitoring check failed: #{e.class}: #{e.message}")
          release_monitor_lock(monitor_state, 'check_failed', e.message) if monitor_state
          nil
        end

        def enabled?(settings = Setting.plugin_redmine_ux_toolkit || {})
          settings.fetch('cron_monitoring_interval', INTERVAL_FOUR_HOURS).to_s == INTERVAL_FOUR_HOURS
        end

        def email_valid?(email)
          value = email.to_s.strip
          value.present? && value.match?(URI::MailTo::EMAIL_REGEXP)
        end

        def monitoring_recipient(settings = Setting.plugin_redmine_ux_toolkit || {},
                                 server_admin_email: nil,
                                 redmine_sender_email: notification_sender_email)
          configured_email = settings['cron_monitoring_email'].to_s.strip
          return email_valid?(configured_email) ? configured_email : nil if configured_email.present?

          return redmine_sender_email if email_valid?(redmine_sender_email)

          fallback_email = normalize_server_admin_email(server_admin_email)
          email_valid?(fallback_email) ? fallback_email : nil
        end

        def notification_sender_email(value = Setting.mail_from)
          address = Mail::Address.new(value.to_s).address.to_s.strip
          email_valid?(address) ? address : nil
        rescue Mail::Field::ParseError
          nil
        end

        def server_admin_email(request_env = nil, process_env: ENV)
          candidates = [
            request_env&.[]('SERVER_ADMIN'),
            request_env&.[]('REDIRECT_SERVER_ADMIN'),
            process_env['SERVER_ADMIN']
          ]

          candidates.each do |candidate|
            email = normalize_server_admin_email(candidate)
            return email if email_valid?(email)
          end

          nil
        end

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

        private

        def inspect_runner(monitor_state, recipient, now, logger)
          cron_state = UxToolkitCronState.find_by(name: UxToolkitCronState::DEFAULT_NAME)
          alert = alert_for(cron_state, now, monitoring_since: monitor_state.created_at)

          if alert.nil?
            release_monitor_lock(monitor_state, 'healthy', I18n.t(:text_ux_toolkit_cron_monitor_healthy), clear_alert: true)
          elsif monitor_state.last_alert_key == alert[:key]
            release_monitor_lock(monitor_state, 'alerted', I18n.t(:text_ux_toolkit_cron_monitor_alert_already_sent))
          else
            deliver_alert(recipient, alert)
            monitor_state.update!(
              last_alerted_at: now,
              last_alert_key: alert[:key],
              last_status: 'alerted',
              last_message: alert[:message],
              locked_until: nil
            )
          end
        rescue StandardError => e
          logger&.error("[Redmine UX Toolkit] Cron monitoring alert failed: #{e.class}: #{e.message}")
          release_monitor_lock(monitor_state, 'email_failed', e.message)
        end

        def alert_for(cron_state, now, monitoring_since: nil)
          if cron_state&.last_status.to_s == 'failed'
            message = cron_state.last_message.to_s.presence || I18n.t(:text_ux_toolkit_cron_monitor_unknown_error)
            return {
              type: 'error',
              key: "error:#{cron_state.last_started_at&.utc&.iso8601}:#{Digest::SHA256.hexdigest(message)}",
              message: message,
              details: {message: message, time: formatted_time(cron_state.last_finished_at || cron_state.last_started_at)}
            }
          end

          last_run = cron_state&.last_started_at
          return nil if last_run && last_run > now - FOUR_HOURS
          return nil if last_run.nil? && monitoring_since && monitoring_since > now - FOUR_HOURS

          {
            type: 'stale',
            key: "stale:#{last_run&.utc&.iso8601 || 'never'}",
            message: I18n.t(:text_ux_toolkit_cron_monitor_stale_message),
            details: {last_run: formatted_time(last_run)}
          }
        end

        def deliver_alert(recipient, alert)
          details = alert[:details].merge(locale: Setting.default_language)
          UxToolkitCronMonitorMailer.alert(recipient, alert[:type], details).deliver_later
        end

        def normalize_server_admin_email(value)
          value.to_s.strip.sub(/\Amailto:/i, '')
        end

        def release_monitor_lock(state, status, message, clear_alert: false)
          attributes = {
            last_status: status,
            last_message: message.to_s.truncate(1000),
            locked_until: nil
          }
          attributes[:last_alert_key] = nil if clear_alert
          state.update!(attributes)
        end

        def cache_gate_open?
          Rails.cache.write(CACHE_KEY, true, expires_in: CACHE_TTL, unless_exist: true)
        rescue StandardError
          true
        end

        def formatted_time(value)
          I18n.with_locale(Setting.default_language) do
            value ? I18n.l(value, format: :long) : I18n.t(:label_ux_toolkit_cron_monitor_never)
          end
        end
      end
    end
  end
end
