# frozen_string_literal: true

require 'mini_magick'
require 'open3'
require 'tempfile'


class MaterialThumbnailGenerator
  SIZE = '180x180'

  class << self
    def generate! material
      new(material).generate!
    end
  end

  def initialize material
    @material = material
    @ffmpeg_stderr = []
  end

  def generate!
    return log_result(:no_file) unless @material.file.attached?
    return log_result(:unsupported_content_type) unless image? || video?

    @material.file.blob.open do |file|
      thumbnail = image? ? image_thumbnail(file.path) : video_thumbnail(file.path)
      return log_result(:generation_failed) unless thumbnail

      return attach_thumbnail(thumbnail)
    end
  rescue ActiveStorage::FileNotFoundError => e
    log_result(:file_not_found, error: e)
  rescue MiniMagick::Error => e
    log_result(:mini_magick_error, error: e)
  rescue ArgumentError, StandardError => e
    log_result(:generation_failed, error: e)
  end

  private

  def image? = content_type.start_with?('image/')

  def video? = content_type.start_with?('video/')

  def content_type
    return nil unless @material.file.attached?

    @material.file.blob.content_type.to_s
  end

  def image_thumbnail path
    image = MiniMagick::Image.open(path)
    image.resize(SIZE)
    image.format('jpg')
    image
  end

  def video_thumbnail path
    [1, 0].each do |seconds|
      tempfile = Tempfile.new(['material-thumbnail', '.jpg'])
      tempfile.close
      ok = extract_video_frame(path, tempfile.path, seconds)
      next unless ok && File.size?(tempfile.path)

      return image_thumbnail(tempfile.path)
    ensure
      tempfile&.unlink
    end

    nil
  end

  def extract_video_frame input_path, output_path, seconds
    _stdout, stderr, status =
        Open3.capture3('ffmpeg',
                       '-y',
                       '-ss', seconds.to_s,
                       '-i', input_path,
                       '-frames:v', '1',
                       '-f', 'image2',
                       output_path)
    @ffmpeg_stderr << stderr if stderr.present?
    status.success?
  rescue Errno::ENOENT => e
    @ffmpeg_stderr << "ffmpeg unavailable: #{ e.message }"
    false
  end

  def attach_thumbnail image
    blob = nil
    File.open(image.path) do |io|
      blob = ActiveStorage::Blob.create_and_upload!(
          io:,
          filename: 'material-thumbnail.jpg',
          content_type: 'image/jpeg')
    end
    @material.thumbnail.attach(blob)
    log_result(:attached)
  rescue StandardError => e
    blob&.purge_later
    log_result(:attach_failed, error: e)
  end

  def log_result result, error: nil
    log_payload = { material_id: @material.id,
                    file_blob_id: file_blob_id,
                    content_type:,
                    result:,
                    error_class: error&.class&.name,
                    error_message: error&.message,
                    ffmpeg_stderr: @ffmpeg_stderr.join("\n").presence }
    if [:attached, :no_file, :unsupported_content_type].include?(result)
      Rails.logger.info("Material thumbnail generation: #{ log_payload.to_json }")
    else
      Rails.logger.warn("Material thumbnail generation: #{ log_payload.to_json }")
    end
    result
  end

  def file_blob_id
    return nil unless @material.file.attached?

    @material.file.blob.id
  end
end
