require 'passive_record/associations/belongs_to'
require 'passive_record/associations/has_one'
require 'passive_record/associations/has_many'
require 'passive_record/associations/has_many_through'

module PassiveRecord
  module Associations
    def associate!(assn)
      @associations ||= []
      @associations += [assn] unless @associations.include?(assn)
      self
    end

    def associations_id_syms
      @associations && @associations.map do |assn|
        if assn.is_a?(HasOneAssociation) || assn.is_a?(BelongsToAssociation)
          (assn.target_name_symbol.to_s + "_id").to_sym
        else
          (assn.target_name_symbol.to_s.singularize + "_ids").to_sym
        end
      end || []
    end

    def belongs_to(parent_name_sym, opts={})
      target_class_name = opts.delete(:class_name) { (parent_name_sym.to_s).split('_').map(&:capitalize).join }
      association = BelongsToAssociation.new(self, target_class_name, parent_name_sym)
      associate!(association)

      define_method(:"#{parent_name_sym}_id") do
        prnt = send(parent_name_sym)
        prnt && prnt.id
      end

      define_method(parent_name_sym) do
        relation = detect_relation(association)
        association.parent_class.find(relation.parent_model_id)
      end

      define_method(:"#{parent_name_sym}=") do |new_parent|
        send(:"#{parent_name_sym}_id=", new_parent.id)
      end

      define_method(:"#{parent_name_sym}_id=") do |new_parent_id|
        relation = detect_relation(association)
        relation.parent_model_id = new_parent_id
      end
    end

    def has_one(child_name_sym)
      child_class_name = (child_name_sym.to_s).split('_').map(&:capitalize).join
      association = HasOneAssociation.new(self, child_class_name, child_name_sym)
      associate!(association)

      define_method(:"#{child_name_sym}_id") do
        chld = send(child_name_sym)
        chld && chld.id
      end

      define_method(child_name_sym) do
        relation = detect_relation(association)
        relation.lookup
      end

      define_method(:"create_#{child_name_sym}") do |attrs={}|
        relation = detect_relation(association)
        relation.create(attrs)
      end

      define_method(:"#{child_name_sym}=") do |new_child|
        send(:"#{child_name_sym}_id=", new_child.id)
      end

      define_method(:"#{child_name_sym}_id=") do |new_child_id|
        relation = detect_relation(association) #relata.detect { |rel| rel.association == association }
        rel = relation.lookup
        rel && rel.send(:"#{relation.parent_model_id_field}=", nil)

        relation.child_class.
          find(new_child_id).
          update(relation.parent_model_id_field => relation.id)
      end
    end

    def has_many(collection_name_sym, opts={})
      target_class_name = opts.delete(:class_name) { (collection_name_sym.to_s).split('_').map(&:capitalize).join.singularize }
      habtm = opts.delete(:habtm) { false }

      association = nil
      if opts.key?(:through)
        through_class_collection_name = opts.delete(:through)

        through_class_name = (through_class_collection_name.to_s).split('_').map(&:capitalize).join.singularize
        base_association = associations.detect { |assn| assn.child_class_name == through_class_name rescue false }

        association = HasManyThroughAssociation.new(
          self, target_class_name, collection_name_sym, through_class_collection_name, base_association, habtm)

        associate!(association)

        define_method(:"#{collection_name_sym}=") do |new_collection|
          send(:"#{collection_name_sym.to_s.singularize}_ids=", new_collection.map(&:id))
        end

        define_method(:"#{collection_name_sym.to_s.singularize}_ids=") do |new_collection_ids|
          relation = detect_relation(association) # relata.detect { |rel| rel.association == association }

          intermediary = relation.intermediary_relation

          # drop all intermediary relations
          intermediary.where( relation.parent_model_id_field => relation.id ).each do |intermediate|
            intermediate.destroy
          end

          # add in new ones...
          singular_target = collection_name_sym.to_s.singularize
          if !(relation.nested_association.is_a?(BelongsToAssociation))
            intermediary.create(
              singular_target + "_ids" => new_collection_ids,
              relation.parent_model_id_field => relation.id
            )
          else
            new_collection_ids.each do |child_id|
              intermediary.create(
                singular_target + "_id" => child_id,
                relation.parent_model_id_field => relation.id
              )
            end
          end
        end
      else
        association = HasManyAssociation.new(self, target_class_name, collection_name_sym)
        associate!(association)

        define_method(:"#{collection_name_sym}=") do |new_collection|
          relation = detect_relation(association)

          # detach existing children...
          relation.all.each do |child|
            child.send(:"#{relation.parent_model_id_field}=", nil)
          end

          # reattach new children
          new_collection.each do |child|
            child.send(:"#{relation.parent_model_id_field}=", relation.id)
          end
        end

        define_method(:"#{collection_name_sym.to_s.singularize}_ids=") do |new_collection_ids|
          relation = detect_relation(association) #@ relata.detect { |rel| rel.association == association }
          send(:"#{collection_name_sym}=", relation.child_class.find(new_collection_ids))
        end
      end

      define_method(collection_name_sym) do
        detect_relation(association)
        # relata.detect { |rel| rel.association == association }
      end

      define_method(:"#{collection_name_sym.to_s.singularize}_ids") do
        begin
          send(collection_name_sym).map(&:id)
        rescue
          binding.pry
        end
      end

      define_method(:"create_#{collection_name_sym.to_s.singularize}") do |attrs={}|
        relation = detect_relation(association) # relata.detect { |rel| rel.association == association }
        relation.create(attrs)
      end
    end

    def has_and_belongs_to_many(collection_name_sym)
      habtm_join_class_name =
        self.name.split('::').last.singularize +
        collection_name_sym.to_s.camelize.singularize +
        "JoinModel"
      inverse_habtm_join_class_name =
        collection_name_sym.to_s.camelize.singularize +
        self.name.split('::').last.singularize +
        "JoinModel"

      module_name = self.name.deconstantize
      module_name = "Object" if module_name.empty?
      intended_module = module_name.constantize

      if (intended_module.const_get(inverse_habtm_join_class_name) rescue false)
        has_many inverse_habtm_join_class_name.underscore.to_sym
        has_many collection_name_sym, :through => inverse_habtm_join_class_name.underscore.to_sym, habtm: true
      else
        auto_collection_sym = self.name.split('::').last.underscore.pluralize.to_sym
        eval <<-ruby
        class #{module_name}::#{habtm_join_class_name}          # class System::UserRoleJoinModel
          include PassiveRecord                                 #   include PassiveRecord
          belongs_to :#{collection_name_sym.to_s.singularize}   #   belongs_to :role
          belongs_to :#{auto_collection_sym.to_s.singularize}   #   belongs_to :user
        end                                                     # end
        ruby
        has_many habtm_join_class_name.underscore.to_sym
        has_many(collection_name_sym, :through => habtm_join_class_name.underscore.to_sym, habtm: true)
      end
    end
  end
end
