class Outfit < ApplicationRecord
  has_many :item_outfit_relationships, :dependent => :destroy
  has_many :worn_item_outfit_relationships, -> { where(is_worn: true) },
    class_name: 'ItemOutfitRelationship'
  has_many :worn_items, through: :worn_item_outfit_relationships, source: :item

  belongs_to :alt_style, optional: true
  belongs_to :pet_state, optional: true # We validate presence below!
  belongs_to :user, optional: true

  validates :name, :presence => {:if => :user_id}, :uniqueness => {:scope => :user_id, :if => :user_id}
  validates :pet_state, presence: {
    message: ->(object, _) do
      if object.biology
        "does not exist for " +
          "species ##{object.biology[:species_id]}, " +
          "color ##{object.biology[:color_id]}, " +
          "pose #{object.biology[:pose]}"
      else
        "must exist"
      end
    end
  }

  before_validation :ensure_unique_name, if: :user_id?

  attr_reader :biology
  delegate :color, to: :pet_state

  scope :wardrobe_order, -> { order('starred DESC', :name) }
  
  class OutfitImage
    def initialize(image_versions)
      @image_versions = image_versions  
    end

    def url
      @image_versions[:large]
    end
    
    def large
      Version.new(@image_versions[:large])
    end
    
    def medium
      Version.new(@image_versions[:medium])
    end
    
    def small
      Version.new(@image_versions[:small])
    end
    
    Version = Struct.new(:url)
  end
  
  def image?
    true
  end
  
  def image
    OutfitImage.new(image_versions)
  end
  
  IMAGE_URL_TEMPLATE = Addressable::Template.new(
    Rails.configuration.impress_2020_origin + 
    "/api/outfitImage{?id,size,updatedAt}"
  )
  def image_versions
    if Rails.env.production?
      # Now, instead of using the saved outfit to S3, we're using out the
      # DTI 2020 API + CDN cache version. We use openneo-assets.net to get
      # around a bug on Neopets petpages with openneo.net URLs.
      base_url = "https://outfits.openneo-assets.net/outfits" +
        "/#{CGI.escape id.to_s}" +
        "/v/#{CGI.escape updated_at.to_i.to_s}"
      {
        large: "#{base_url}/600.png",
        medium: "#{base_url}/300.png",
        small: "#{base_url}/150.png",
      }
    else
      # In development, just talk to our local Impress 2020 directly.
      build_url = -> size {
        IMAGE_URL_TEMPLATE.expand(
          id: id, size: size, updatedAt: updated_at.to_i
        ).to_s
      }
      {
        large: build_url.call("600"),
        medium: build_url.call("300"),
        small: build_url.call("150"),
      }
    end

    # NOTE: Below is the previous code that uses the saved outfits!
    # {}.tap do |versions|
    #   versions[:large] = image.url
    #   image.versions.each { |name, version| versions[name] = version.url }
    # end
  end
  
  def as_json(more_options={})
    serializable_hash(
      only: [:id, :name, :pet_state_id, :starred, :created_at, :updated_at,
        :alt_style_id],
      methods: [:color_id, :species_id, :pose, :item_ids, :user]
    )
  end

  def color_id
    pet_state.pet_type.color_id
  end

  def species_id
    pet_state.pet_type.species_id
  end

  def pose
    pet_state.pose
  end

  def biology=(biology)
    @biology = biology.slice(:species_id, :color_id, :pose, :pet_state_id)

    begin
      if @biology[:pet_state_id]
        self.pet_state = PetState.find(@biology[:pet_state_id])
      else
        pet_type = PetType.where(
          species_id: @biology[:species_id],
          color_id: @biology[:color_id],
        ).first!
        self.pet_state = pet_type.pet_states.with_pose(@biology[:pose]).
          emotion_order.first!
      end
    rescue ActiveRecord::RecordNotFound
      # If there's no such pet state (which shouldn't happen normally in-app),
      # we don't set `pet_state` but we keep `@biology` for validation.
    end
  end

  def item_ids
    rels = item_outfit_relationships
    {
      worn: rels.filter { |r| r.is_worn? }.map { |r| r.item_id },
      closeted: rels.filter { |r| !r.is_worn? }.map { |r| r.item_id }
    }
  end

  def item_ids=(item_ids)
    # Ensure there are no duplicates between the worn/closeted IDs. If an ID is
    # present in both, it's kept in `worn` and removed from `closeted`.
    worn_item_ids = item_ids.fetch(:worn, []).uniq
    closeted_item_ids = item_ids.fetch(:closeted, []).uniq
    closeted_item_ids.reject! { |id| worn_item_ids.include?(id) }

    # Set the worn and closeted item outfit relationships. If there are any
    # others attached to this outfit, they are implicitly deleted.
    new_relationships = []
    new_relationships += worn_item_ids.map do |item_id|
      ItemOutfitRelationship.new(item_id: item_id, is_worn: true)
    end
    new_relationships += closeted_item_ids.map do |item_id|
      ItemOutfitRelationship.new(item_id: item_id, is_worn: false)
    end
    self.item_outfit_relationships = new_relationships
  end

  def ensure_unique_name
    # If no name was provided, start with "Untitled outfit".
    self.name = "Untitled outfit" if name.blank?

    # Strip whitespace from the name.
    self.name.strip!

    # Get the base name of the provided name, without any "(1)" suffixes.
    base_name = name.sub(/\s*\([0-9]+\)\z/, '')

    # Find the user's other outfits that start with the same base name, and get
    # *their* names, with whitespace stripped.
    existing_outfits = self.user.outfits.
      where("name LIKE ?", Outfit.sanitize_sql_like(base_name) + "%")
    existing_outfits = existing_outfits.where("id != ?", id) unless id.nil?
    existing_names = existing_outfits.map(&:name).map(&:strip)

    # Try the provided name first, but if it's taken, add a "(1)" suffix and
    # keep incrementing it until it's not.
    i = 1
    while existing_names.include?(name)
      self.name = "#{base_name} (#{i})"
      i += 1
    end
  end
end