# frozen_string_literal: true

module AppInfo::Helper
  module FileTypeDetection
    # Detect file type by reading file header
    #
    # TODO: This can be better solution, if anyone knows, tell me please.
    def file_type(file)
      header_hex = ::File.read(file, 100)
      case header_hex
      when ZIP_RETGEX
        detect_zip_file(file)
      when PE_REGEX
        AppInfo::Format::PE
      when PLIST_REGEX, BPLIST_REGEX
        AppInfo::Format::MOBILEPROVISION
      else
        AppInfo::Format::UNKNOWN
      end
    end

    private

    # Detect file type for zip files
    def detect_zip_file(file)
      Zip.warn_invalid_date = false
      zip_file = Zip::File.open(file)

      return AppInfo::Format::PROGUARD if proguard_clues?(zip_file)
      return AppInfo::Format::APK if apk_clues?(zip_file)
      return AppInfo::Format::AAB if aab_clues?(zip_file)
      return AppInfo::Format::MACOS if macos_clues?(zip_file)
      return AppInfo::Format::PE if pe_clues?(zip_file)
      return AppInfo::Format::HAP if hap_clues?(zip_file)
      return AppInfo::Format::HAPP if happ_clues?(zip_file)
      return AppInfo::Format::UNKNOWN unless clue = other_clues?(zip_file)

      clue
    ensure
      zip_file.close
    end

    # Check for Proguard clues in zip file
    def proguard_clues?(zip_file)
      !zip_file.glob('*mapping*.txt').empty?
    end

    # Check for APK clues in zip file
    def apk_clues?(zip_file)
      !zip_file.find_entry('AndroidManifest.xml').nil? &&
        !zip_file.find_entry('classes.dex').nil?
    end

    # Check for AAB clues in zip file
    def aab_clues?(zip_file)
      !zip_file.find_entry('base/manifest/AndroidManifest.xml').nil? &&
        !zip_file.find_entry('BundleConfig.pb').nil?
    end

    # Check for macOS clues in zip file
    def macos_clues?(zip_file)
      !zip_file.glob('*/Contents/MacOS/*').empty? &&
        !zip_file.glob('*/Contents/Info.plist').empty?
    end

    # Check for PE clues in zip file
    def pe_clues?(zip_file)
      !zip_file.glob('*.exe').empty?
    end

    # Check for HAP clues in zip file
    def hap_clues?(zip_file)
      !zip_file.find_entry('pack.info').nil? && !zip_file.find_entry('module.json').nil?
    end

    # Check for HAPP clues in zip file
    def happ_clues?(zip_file)
      pack_info_count = 0
      hap_count = 0

      zip_file.each do |f|
        path = f.name

        if path == 'pack.info'
          pack_info_count += 1
        elsif path.end_with?('.hap')
          hap_count += 1
        else
          return false
        end
      end

      pack_info_count == 1 && hap_count >= 1
    end

    # Check for other clues in zip file
    def other_clues?(zip_file)
      zip_file.each do |f|
        path = f.name

        return AppInfo::Format::IPA if path.include?('Payload/') && path.end_with?('Info.plist')
        return AppInfo::Format::DSYM if path.include?('Contents/Resources/DWARF/')
      end
    end

    ZIP_RETGEX = /^\x50\x4b\x03\x04/.freeze
    PE_REGEX = /^MZ/.freeze
    PLIST_REGEX = /\x3C\x3F\x78\x6D\x6C/.freeze
    BPLIST_REGEX = /^\x62\x70\x6C\x69\x73\x74/.freeze
  end
end
