# frozen_string_literal: true

class StorageLocation < ApplicationRecord
  include Cloneable
  include Discard::Model
  ID_PREFIX = 'SL'
  include PrefixedIdModel
  include Shareable
  include SearchableModel
  include Rails.application.routes.url_helpers

  default_scope -> { kept }

  has_one_attached :image

  belongs_to :team
  belongs_to :parent, class_name: 'StorageLocation', optional: true
  belongs_to :created_by, class_name: 'User'

  has_many :storage_location_repository_rows, inverse_of: :storage_location, dependent: :destroy
  has_many :storage_locations, foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy
  has_many :repository_rows, through: :storage_location_repository_rows

  validates :name, length: { maximum: Constants::NAME_MAX_LENGTH }
  validate :parent_same_team, if: -> { parent.present? }
  validate :parent_validation, if: -> { parent.present? }
  validate :no_grid_options, if: -> { !container }
  validate :no_dimensions, if: -> { !with_grid? }

  scope :readable_by_user, (lambda do |user, team = user.current_team|
    next StorageLocation.none unless team.permission_granted?(user, TeamPermissions::STORAGE_LOCATIONS_READ)

    where(team: team)
  end)

  after_discard do
    StorageLocation.where(parent_id: id).find_each(&:discard)
    storage_location_repository_rows.each(&:discard)
  end

  def self.permission_class
    StorageLocation
  end

  def shared_with?(team)
    return false if self.team == team

    (root? ? self : root_storage_location).private_shared_with?(team)
  end

  def root?
    parent_id.nil?
  end

  def root_storage_location
    return self if root?

    storage_location = self

    storage_location = storage_location.parent while storage_location.parent_id

    storage_location
  end

  def empty?
    storage_location_repository_rows.count.zero?
  end

  def duplicate!(user, team)
    ActiveRecord::Base.transaction do
      new_storage_location = dup
      new_storage_location.name = next_clone_name
      new_storage_location.team = team unless parent_id
      new_storage_location.created_by = user
      new_storage_location.save!
      copy_image(self, new_storage_location)
      recursive_duplicate(id, new_storage_location.id, user, new_storage_location.team)
      new_storage_location
    rescue ActiveRecord::RecordInvalid
      false
    end
  end

  def breadcrumbs(readable: true)
    url = if container
            storage_location_path(id)
          else
            storage_locations_path(parent_id: id)
          end

    return [{ name: (readable ? name : code), url: url }] if root?

    parent.breadcrumbs(readable: readable) + [{ name: (readable ? name : code), url: url }]
  end

  def with_grid?
    metadata['display_type'] == 'grid'
  end

  def grid_size
    metadata['dimensions'] if with_grid?
  end

  def available_positions
    return unless with_grid?

    occupied_positions = storage_location_repository_rows.pluck(:metadata).map { |metadata| metadata['position'] }

    rows = {}

    grid_size[0].times do |row|
      rows_cells = []
      grid_size[1].times.filter_map do |col|
        rows_cells.push(col + 1) if occupied_positions.exclude?([row + 1, col + 1])
      end
      rows[row + 1] = rows_cells unless rows_cells.empty?
    end

    rows
  end

  def self.shared_sql_select(user)
    shared_write_value = TeamSharedObject.permission_levels['shared_write']
    team_id = user.current_team.id

    case_statement = <<-SQL.squish
      CASE
        WHEN EXISTS (
          SELECT 1 FROM team_shared_objects
          WHERE team_shared_objects.shared_object_id = storage_locations.id
            AND team_shared_objects.shared_object_type = 'StorageLocation'
            AND storage_locations.team_id = :team_id
          ) THEN 1
        WHEN EXISTS (
          SELECT 1 FROM team_shared_objects
          WHERE team_shared_objects.shared_object_id = storage_locations.id
            AND team_shared_objects.shared_object_type = 'StorageLocation'
            AND team_shared_objects.team_id = :team_id
        ) THEN
            CASE
              WHEN EXISTS (
                  SELECT 1 FROM team_shared_objects
                  WHERE team_shared_objects.shared_object_id = storage_locations.id
                    AND team_shared_objects.shared_object_type = 'StorageLocation'
                    AND team_shared_objects.permission_level = :shared_write_value
                    AND team_shared_objects.team_id = :team_id
                ) THEN 2
              ELSE 3
            END
        ELSE 4
      END as shared
    SQL

    ActiveRecord::Base.sanitize_sql_array(
      [case_statement, { team_id: team_id, shared_write_value: shared_write_value }]
    )
  end

  def self.storage_locations_enabled?
    ApplicationSettings.instance.values['storage_locations_enabled']
  end

  private

  def recursive_duplicate(old_parent_id = nil, new_parent_id = nil, user = nil, team = nil)
    StorageLocation.where(parent_id: old_parent_id).find_each do |child|
      new_child = child.dup
      new_child.parent_id = new_parent_id
      new_child.team = team
      new_child.created_by = user
      new_child.save!
      copy_image(child, new_child)
      recursive_duplicate(child.id, new_child.id, user, team)
    end
  end

  def copy_image(old_storage_location, new_storage_location)
    return unless old_storage_location.image.attached?

    old_blob = old_storage_location.image.blob
    old_blob.open do |tmp_file|
      to_blob = ActiveStorage::Blob.create_and_upload!(
        io: tmp_file,
        filename: old_blob.filename,
        metadata: old_blob.metadata
      )
      new_storage_location.image.attach(to_blob)
    end
  end

  def self.inner_storage_locations(team, storage_location = nil)
    entry_point_condition = storage_location ? 'parent_id = ?' : 'parent_id IS NULL'

    inner_storage_locations_sql =
      "WITH RECURSIVE inner_storage_locations(id, selected_storage_locations_ids) AS (
        SELECT id, ARRAY[id]
        FROM storage_locations
        WHERE team_id = ? AND #{entry_point_condition}
        UNION ALL
        SELECT storage_locations.id, selected_storage_locations_ids || storage_locations.id
        FROM inner_storage_locations
        JOIN storage_locations ON storage_locations.parent_id = inner_storage_locations.id
        WHERE NOT storage_locations.id = ANY(selected_storage_locations_ids)
      )
      SELECT id FROM inner_storage_locations ORDER BY selected_storage_locations_ids".gsub(/\n|\t/, ' ').squeeze(' ')

    if storage_location.present?
      where("storage_locations.id IN (#{inner_storage_locations_sql})", team.id, storage_location.id)
    else
      where("storage_locations.id IN (#{inner_storage_locations_sql})", team.id)
    end
  end

  def parent_validation
    if parent.id == id
      errors.add(:parent, I18n.t('activerecord.errors.models.storage_location.attributes.parent_storage_location'))
    elsif StorageLocation.inner_storage_locations(team, self).exists?(id: parent_id)
      errors.add(:parent, I18n.t('activerecord.errors.models.project_folder.attributes.parent_storage_location_child'))
    end
  end

  def no_grid_options
    errors.add(:metadata, I18n.t('activerecord.errors.models.storage_location.attributes.metadata.invalid')) if metadata['display_type'] || metadata['dimensions']
  end

  def parent_same_team
    errors.add(:parent, I18n.t('activerecord.errors.models.storage_location.attributes.parent_storage_location_team')) if parent.team != team
  end

  def no_dimensions
    errors.add(:metadata, I18n.t('activerecord.errors.models.storage_location.attributes.metadata.invalid')) if !with_grid? && metadata['dimensions']
  end
end
