require 'fastimage'

module Deliver
  class AppScreenshotValidator
    # A simple structure that holds error information as well as formatted error messages consistently
    # Set `to_skip` to `true` when just needing to skip uploading rather than causing a crash.
    class ValidationError
      # Constants that can be given to `type` param
      INVALID_SCREEN_SIZE = 'Invalid screen size'.freeze
      UNACCEPTABLE_DEVICE = 'Not an accepted App Store Connect device'.freeze
      INVALID_FILE_EXTENSION = 'Invalid file extension'.freeze
      FILE_EXTENSION_MISMATCH = 'File extension mismatches its image format'.freeze

      attr_reader :type, :path, :debug_info, :to_skip

      def initialize(type: nil, path: nil, debug_info: nil, to_skip: false)
        @type = type
        @path = path
        @debug_info = debug_info
        @to_skip = to_skip
      end

      def to_s
        "#{to_skip ? '🏃 Skipping' : '🚫 Error'}: #{path} - #{type} (#{debug_info})"
      end

      def inspect
        "\"#{type}\""
      end
    end

    # Access each array by symbol returned from FastImage.type
    ALLOWED_SCREENSHOT_FILE_EXTENSION = { png: ['png', 'PNG'], jpeg: ['jpg', 'JPG', 'jpeg', 'JPEG'] }.freeze

    APP_SCREENSHOT_SPEC_URL = 'https://help.apple.com/app-store-connect/#/devd274dd925'.freeze

    # Validate a screenshot and inform an error message via `errors` parameter. `errors` is mutated
    # to append the messages and each message should contain the corresponding path to let users know which file is throwing the error.
    #
    # @param screenshot [AppScreenshot]
    # @param errors [Array<Deliver::AppScreenshotValidator::ValidationError>] Pass an array object to add validation errors when detecting errors.
    #   This will be mutated to add more error objects as validation detects errors.
    # @return [Boolean] true if given screenshot is valid
    def self.validate(screenshot, errors)
      # Given screenshot will be diagnosed and errors found are accumulated
      errors_found = []

      validate_screen_size(screenshot, errors_found)
      validate_device_type(screenshot, errors_found)
      validate_file_extension_and_format(screenshot, errors_found)

      # Merge errors found into given errors array
      errors_found.each { |error| errors.push(error) }
      errors_found.empty?
    end

    def self.validate_screen_size(screenshot, errors_found)
      if screenshot.screen_size.nil?
        errors_found << ValidationError.new(type: ValidationError::INVALID_SCREEN_SIZE,
                                            path: screenshot.path,
                                            debug_info: "Actual size is #{get_formatted_size(screenshot)}. See the specifications to fix #{APP_SCREENSHOT_SPEC_URL}")
      end
    end

    # Checking if the device type exists in spaceship
    # Ex: iPhone 6.1 inch isn't supported in App Store Connect but need
    # to have it in there for frameit support
    def self.validate_device_type(screenshot, errors_found)
      if !screenshot.screen_size.nil? && screenshot.device_type.nil?
        errors_found << ValidationError.new(type: ValidationError::UNACCEPTABLE_DEVICE,
                                            path: screenshot.path,
                                            debug_info: "Screen size #{screenshot.screen_size} is not accepted. See the specifications to fix #{APP_SCREENSHOT_SPEC_URL}",
                                            to_skip: true)
      end
    end

    def self.validate_file_extension_and_format(screenshot, errors_found)
      extension = File.extname(screenshot.path).delete('.')
      valid_file_extensions = ALLOWED_SCREENSHOT_FILE_EXTENSION.values.flatten
      is_valid_extension = valid_file_extensions.include?(extension)

      unless is_valid_extension
        errors_found << ValidationError.new(type: ValidationError::INVALID_FILE_EXTENSION,
                                            path: screenshot.path,
                                            debug_info: "Only #{valid_file_extensions.join(', ')} are allowed")
      end

      format = FastImage.type(screenshot.path)
      is_extension_matched = ALLOWED_SCREENSHOT_FILE_EXTENSION[format] &&
                             ALLOWED_SCREENSHOT_FILE_EXTENSION[format].include?(extension)

      # This error only appears when file extension is valid
      if is_valid_extension && !is_extension_matched
        expected_extension = ALLOWED_SCREENSHOT_FILE_EXTENSION[format].first
        expected_filename = File.basename(screenshot.path, File.extname(screenshot.path)) + ".#{expected_extension}"
        errors_found << ValidationError.new(type: ValidationError::FILE_EXTENSION_MISMATCH,
                                            path: screenshot.path,
                                            debug_info: %(Actual format is "#{format}". Rename the filename to "#{expected_filename}".))
      end
    end

    def self.get_formatted_size(screenshot)
      size = FastImage.size(screenshot.path)
      return size.join('x') if size
      nil
    end
  end
end
