# frozen_string_literal: true

# Flow specific methods for Spree::Variant
# Spree save all the prices inside Variant object. We choose to have a cache jsonb field named flow_data that will
# hold all important Flow sync data for specific experiences.
module Spree
  module FlowIoVariantDecorator
    def self.prepended(base)
      base.serialize :meta, ActiveRecord::Coders::JSON.new(symbolize_keys: true)

      base.store_accessor :meta, :flow_data

      # after every save we sync product we generate sh1 checksums to update only when change happend
      base.after_save :sync_product_to_flow
    end

    def experiences
      flow_data&.[]('exp')
    end

    def add_flow_io_experience_data(exp, value)
      raise ArgumentError, 'Value should be a hash' unless value.is_a?(Hash)

      self.flow_data = flow_data || {}
      self.flow_data['exp'] ||= {} # rubocop:disable Style/RedundantSelf
      self.flow_data['exp'][exp] = value # rubocop:disable Style/RedundantSelf
    end

    # clears flow_data from the records
    def truncate_flow_data
      flow_data&.[]('exp')&.keys&.each do |exp_key|
        break unless (product = self.product)

        remove_experience_from_product(exp_key, product)
      end

      meta.delete(:flow_data)
      update_column(:meta, meta.to_json)
    end

    def remove_experience_from_product(exp_key, product)
      return unless (zone = Spree::Zones::Product.find_by(name: exp_key.titleize))

      zone_ids = product.zone_ids || []
      zone_id_string = zone.id.to_s
      return unless zone_ids.include?(zone_id_string)

      product.zone_ids = zone_ids - [zone_id_string]
      product.update_columns(meta: product.meta.to_json)
    end

    def sync_flow_info?
      if FlowcommerceSpree::API_KEY.blank? || FlowcommerceSpree::API_KEY == 'test_key'
        return { error: 'Api Keys not configured' }
      end
      return { error: 'Price is 0' } if price == 0
      return { error: 'Country of Origin is empty.' } unless country_of_origin
    end

    # upload product variant to Flow's Product Catalog
    def sync_product_to_flow
      error = sync_flow_info?
      return error if error.present?

      update_flow_data
    end

    def update_flow_data
      additional_attrs = {}
      attr_name = nil
      export_required = false
      FlowcommerceSpree::Config.additional_attributes[self.class.name.tableize.tr('/', '_').to_sym]&.each do |attr_item|
        attr_name = attr_item[0]
        # Flow.io could require a different attribute name, as in case of Fulfil's :customs_description - it has the
        # export_name `:materials` for flow.io. That's why 1st we're checking if an export_name is defined for the
        # attribute.
        attr_flowcommerce_name = attr_item[1][:export_name] || attr_name
        export_required = attr_item[1][:export] == :required
        attr_value = __send__(attr_name)
        break if export_required && attr_value.blank?

        additional_attrs[attr_flowcommerce_name] = attr_value if attr_value
      end

      if export_required && additional_attrs[attr_value].blank?
        return { error: "Variant with sku = #{sku} has no #{attr_name}" }
      end

      flow_item     = to_flowcommerce_item(additional_attrs)
      flow_item_sh1 = Digest::SHA1.hexdigest(flow_item.to_json)

      # skip if sync not needed
      return { error: 'Synchronization not needed' } if flow_data&.[](:last_sync_sh1) == flow_item_sh1

      response = FlowcommerceSpree.client.items.put_by_number(FlowcommerceSpree::ORGANIZATION, sku, flow_item)
      self.flow_data ||= {}
      self.flow_data[:last_sync_sh1] = flow_item_sh1

      # after successful put, write cache
      update_column(:meta, meta.to_json)

      FlowcommerceSpree::ImportItemWorker.perform_async(sku)

      response
    rescue Net::OpenTimeout => e
      { error: e.message }
    end

    def flow_prices(flow_exp)
      flow_data&.dig(:exp, flow_exp, :prices) || []
    end

    # returns price bound to local experience
    def flow_local_price(flow_exp)
      price_object = flow_prices(flow_exp)&.first
      amount = price_object&.[](:amount) || price
      currency = price_object&.[](:currency) || cost_currency
      Spree::Price.new(variant_id: id, currency: currency, amount: amount)
    end

    def price_in_zone(currency, product_zone)
      flow_experience_key = product_zone&.flow_data&.[]('key')
      return flow_local_price(flow_experience_key) if flow_experience_key.present?

      price_in(currency)
    end

    def all_prices_in_zone(product_zone)
      all_prices = prices.map { |price| { currency: price.currency, amount: (price.amount&.round || 0).to_s } }

      flow_experience_key = product_zone&.flow_data&.[]('key')
      return all_prices if flow_experience_key.blank?

      flow_price = flow_local_price(flow_experience_key)
      all_prices << { currency: flow_price.currency, amount: (flow_price.amount&.round || 0).to_s }
      all_prices
    end

    # creates object for flow api
    def to_flowcommerce_item(additional_attrs)
      # add product categories
      categories = []
      taxon = product.taxons.first
      current_taxon = taxon
      while current_taxon
        categories.unshift current_taxon.name
        current_taxon = current_taxon.parent
      end

      images = if (image = product.images.first || product.variant_images.first)
                 asset_host_scheme = ENV.fetch('ASSET_HOST_PROTOCOL', 'https')
                 asset_host = ENV.fetch('ASSET_HOST', 'staging.mejuri.com')
                 large_image_uri = URI(image.attachment(:large))
                 product_image_uri = URI(image.attachment.url(:product))
                 large_image_uri.scheme ||= asset_host_scheme
                 product_image_uri.scheme ||= asset_host_scheme
                 large_image_uri.host ||= asset_host
                 product_image_uri.host ||= asset_host

                 [{ url: large_image_uri.to_s, tags: ['checkout'] },
                  { url: product_image_uri.to_s, tags: ['thumbnail'] }]
               else
                 []
               end

      Io::Flow::V0::Models::ItemForm.new(
        number: sku,
        locale: 'en_US',
        language: 'en',
        name: product.name,
        description: product.description,
        currency: cost_currency,
        price: price.to_f,
        images: images,
        categories: categories,
        attributes: common_attrs(taxon).merge!(additional_attrs)
      )
    end

    def common_attrs(taxon)
      {
        weight: weight.to_s,
        height: height.to_s,
        width: width.to_s,
        depth: depth.to_s,
        is_master: is_master ? 'true' : 'false',
        product_id: product_id.to_s,
        tax_category: product.tax_category_id.to_s,
        product_description: product.description,
        product_shipping_category: product.shipping_category_id ? shipping_category.name : nil,
        product_meta_title: taxon&.meta_title.to_s,
        product_meta_description: taxon&.meta_description.to_s,
        product_meta_keywords: taxon&.meta_keywords.to_s,
        product_slug: product.slug
      }.select { |_k, v| v.present? }
    end

    # gets flow catalog item, and imports it
    # called from flow:sync_localized_items rake task
    def flow_import_item(item_hash, experience_key: nil)
      # If experience not specified, get it from the local hash of imported variant
      experience_key ||= item_hash.dig(:local, :experience, :key)
      current_experience_meta = item_hash.delete(:local)

      # Do not repeatedly store Experience data - this is stored in Spree::Zones::Product
      current_experience_meta.delete(:experience)
      add_flow_io_experience_data(experience_key, current_experience_meta)
      self.flow_data.merge!(item_hash)

      update_column(:meta, meta.to_json)
    end

    Spree::Variant.prepend(self) if Spree::Variant.included_modules.exclude?(self)
  end
end
