# frozen_string_literal: true

module Granblue
  module Downloaders
    # Downloads weapon image assets from the game server in different sizes and variants.
    # Handles weapon-specific variants like base art, transcendence art, and elemental variants.
    #
    # @example Download images for a specific weapon
    #   downloader = WeaponDownloader.new("1040001000", storage: :both)
    #   downloader.download
    #
    # @note Weapon images come in multiple variants based on uncap and element status
    # @note Supports transcendence variants and element-specific variants
    # @see ElementalWeaponDownloader for handling multi-element weapons
    class WeaponDownloader < BaseDownloader
      # Override SIZES to include 'base' for b directory images
      SIZES = %w[main grid square base].freeze

      # Maps internal element ID to game source offset
      # :note means use _note suffix on base ID, integer means add offset to base ID
      # Internal: 0=Null, 1=Wind, 2=Fire, 3=Water, 4=Earth, 5=Dark, 6=Light
      # Game order: Fire (base), Water (+100), Earth (+200), Wind (+300), Light (+400), Dark (+500)
      ELEMENT_SOURCE_MAP = {
        0 => :note,  # {id}_note -> {id}_0 (Null/no element)
        1 => 300,    # {id+300} -> {id}_1 (Wind)
        2 => 0,      # {id}     -> {id}_2 (Fire)
        3 => 100,    # {id+100} -> {id}_3 (Water)
        4 => 200,    # {id+200} -> {id}_4 (Earth)
        5 => 500,    # {id+500} -> {id}_5 (Dark)
        6 => 400     # {id+400} -> {id}_6 (Light)
      }.freeze
      # Downloads images for all variants of a weapon based on their uncap status.
      # Overrides {BaseDownloader#download} to handle weapon-specific variants.
      #
      # @param selected_size [String] The size to download. If nil, downloads all sizes.
      # @return [void]
      # @note Skips download if weapon is not found in database
      # @note Downloads transcendence variants only if weapon has those uncaps
      # @see #download_variants
      def download(selected_size = nil)
        weapon = Weapon.find_by(granblue_id: @id)
        return unless weapon

        download_variants(weapon, selected_size)
      end

      private

      # Downloads all variants of a weapon's images
      #
      # @param weapon [Weapon] Weapon model instance to download images for
      # @param selected_size [String] The size to download. If nil, downloads all sizes.
      # @return [void]
      # @note Only downloads variants that should exist based on weapon uncap status
      # @note Handles special transcendence art variants for transcendable weapons
      # @note Downloads element variants for element-changeable weapons
      def download_variants(weapon, selected_size = nil)
        # All weapons have base variant
        variants = [@id]

        # Add transcendence variants if available
        variants.push("#{@id}_02", "#{@id}_03") if weapon.transcendence

        log_info "Downloading weapon variants: #{variants.join(', ')}" if @verbose

        variants.each do |variant_id|
          download_variant(variant_id, selected_size)
        end

        # Download element variants for element-changeable weapons
        download_element_variants(selected_size) if Weapon.element_changeable?(weapon)
      end

      # Downloads a specific variant's images in all sizes
      #
      # @param variant_id [String] Weapon variant ID (e.g., "1040001000_02")
      # @param selected_size [String] The size to download. If nil, downloads all sizes.
      # @return [void]
      # @note Downloads all size variants (main/grid/square) for the given variant
      def download_variant(variant_id, selected_size = nil)
        log_info "-> #{variant_id}" if @verbose
        return if @test_mode

        sizes = selected_size ? [selected_size] : SIZES

        sizes.each_with_index do |size, index|
          path = download_path(size)
          url = build_variant_url(variant_id, size)
          process_download(url, size, path, last: index == sizes.size - 1)
        end
      end

      # Downloads all element variants for element-changeable weapons
      # Maps game URLs to internal element IDs for storage
      #
      # @param selected_size [String] The size to download. If nil, downloads all sizes.
      # @return [void]
      def download_element_variants(selected_size = nil)
        base_id = @id.to_i
        log_info "Downloading element variants for #{@id}" if @verbose

        ELEMENT_SOURCE_MAP.each do |element_id, source|
          if source == :note
            # Element 0: download from {id}_note, save as {id}_0
            source_id = "#{base_id}_note"
          else
            # Elements 1-6: download from {id + offset}, save as {id}_{element}
            source_id = (base_id + source).to_s
          end
          target_suffix = "_#{element_id}"

          download_element_variant(source_id, target_suffix, selected_size)
        end
      end

      # Downloads a single element variant in all sizes
      #
      # @param source_id [String] Source ID to download from (e.g., "1040001000_note" or "1040001100")
      # @param target_suffix [String] Suffix to use for storage (e.g., "_0" or "_1")
      # @param selected_size [String] The size to download. If nil, downloads all sizes.
      # @return [void]
      def download_element_variant(source_id, target_suffix, selected_size = nil)
        return if @test_mode

        sizes = selected_size ? [selected_size] : SIZES

        sizes.each do |size|
          path = download_path(size)
          source_url = build_variant_url(source_id, size)
          target_filename = "#{@id}#{target_suffix}.#{size == 'base' ? 'png' : 'jpg'}"

          process_element_download(source_url, size, path, target_filename)
        end
      end

      # Process download for element variant (source URL differs from target filename)
      #
      # @param url [String] Source URL to download from
      # @param size [String] Image size variant
      # @param path [String] Local directory path
      # @param filename [String] Target filename to save as
      # @return [void]
      def process_element_download(url, size, path, filename)
        s3_key = build_s3_key(size, filename)
        local_path = "#{path}/#{filename}"

        return unless should_download?(local_path, s3_key)

        log_info "\t├ #{size}: #{url} -> #{filename}" if @verbose

        case @storage
        when :local
          download_element_to_local(url, local_path)
        when :s3
          stream_element_to_s3(url, s3_key)
        when :both
          download_element_to_both(url, local_path, s3_key)
        end
      rescue OpenURI::HTTPError
        log_info "\t404 returned\t#{url}" if @verbose
      end

      # Download element variant to local storage
      def download_element_to_local(url, local_path)
        URI.parse(url).open do |file|
          IO.copy_stream(file, local_path)
        end
      end

      # Stream element variant to S3
      def stream_element_to_s3(url, s3_key)
        return if !@force && @aws_service&.file_exists?(s3_key)

        URI.parse(url).open do |file|
          @aws_service.upload_stream(file, s3_key)
        end
      end

      # Download element variant to both local and S3
      def download_element_to_both(url, local_path, s3_key)
        download = URI.parse(url).open

        # Write to local file
        IO.copy_stream(download, local_path)

        # Reset file pointer for S3 upload
        download.rewind

        # Upload to S3 if force or if it doesn't exist
        return if !@force && @aws_service&.file_exists?(s3_key)

        @aws_service.upload_stream(download, s3_key)
      end

      # Builds URL for a specific variant and size
      #
      # @param variant_id [String] Weapon variant ID
      # @param size [String] Image size variant ("main", "grid", "square", or "base")
      # @return [String] Complete URL for downloading the image
      def build_variant_url(variant_id, size)
        directory = directory_for_size(size)
        if size == 'base'
          "#{@base_url}/#{directory}/#{variant_id}.png"
        else
          "#{@base_url}/#{directory}/#{variant_id}.jpg"
        end
      end

      # Gets object type for file paths and storage keys
      # @return [String] Returns "weapon"
      def object_type
        'weapon'
      end

      # Gets base URL for weapon assets
      # @return [String] Base URL for weapon images
      def base_url
        'https://prd-game-a-granbluefantasy.akamaized.net/assets_en/img/sp/assets/weapon'
      end

      # Gets directory name for a size variant
      #
      # @param size [String] Image size variant
      # @return [String] Directory name in game asset URL structure
      # @note Maps "main" -> "ls", "grid" -> "m", "square" -> "s", "base" -> "b"
      def directory_for_size(size)
        case size.to_s
        when 'main' then 'ls'
        when 'grid' then 'm'
        when 'square' then 's'
        when 'base' then 'b'
        end
      end
    end
  end
end
