# typed: strict
# frozen_string_literal: true
extend T::Sig

# Set up the database schema for testing
ActiveRecord::Schema.define do
  create_table :vehicle_lots do |t|
    t.string :name
    t.timestamps
  end

  create_base_table :vehicles do |t|
    t.string :name
    t.references :vehicle_lot, foreign_key: { to_table: :vehicle_lots }
    t.timestamps

    t.create_aux :car do |t|
      t.string :fuel_type
      t.decimal :engine_size, precision: 3, scale: 1
    end
  end

  change_base_table :vehicles do |t|
    t.create_aux :boat do |t|
      t.boolean :only_freshwater
    end

    t.create_aux :plane do |t|
      t.integer :engine_type
    end
  end

  create_base_table :people do |t|
    t.string :name
    t.timestamps

    t.create_aux :driver do |t|
      t.integer :license_number, index: true, null: false
      t.references :car,
                   foreign_key: {
                     to_table: :vehicles_car_aux,
                     primary_key: :base_table_id
                   }
    end

    t.create_aux :captain do |t|
      t.references :boat,
                   null: false,
                   foreign_key: {
                     to_table: :vehicles_boat_aux,
                     primary_key: :base_table_id
                   }
    end

    t.create_aux :passenger do |t|
      t.references :boat,
                   null: false,
                   foreign_key: {
                     to_table: :vehicles_boat_aux,
                     primary_key: :base_table_id
                   }
    end
  end

  create_base_table :relationship_joins do |t|
    t.create_aux :doctor_patient do |t|
      t.integer :num_exams
      t.references :doctor, foreign_key: { to_table: :people }
      t.references :patient, foreign_key: { to_table: :people }
    end

    t.create_aux :employer_employee do |t|
      t.boolean :signed_nda
      t.references :employer, foreign_key: { to_table: :people }
      t.references :employee, foreign_key: { to_table: :people }
    end
  end

  create_base_table :utensils do |t|
    t.string :name
    t.string :material
    t.timestamps

    t.create_aux :fork do |t|
      t.integer :num_tongs
    end

    t.create_aux :spoon do |t|
      t.string :curvature
    end
  end

  create_aux_table :people, :reader do |t|
    t.integer :reading_speed
    t.integer :read_book_joins_count
  end

  create_table :books do |t|
    t.string :title
    t.string :author
    t.integer :pages
    t.integer :read_book_joins_count
    t.timestamps
  end

  create_table :read_book_joins,
               id: false,
               primary_key: %i[book_id reader_id] do |t|
    t.references :book, foreign_key: { to_table: :books }
    t.references :reader,
                 foreign_key: {
                   to_table: :people_reader_aux,
                   primary_key: :base_table_id
                 }
  end
end

class Vehicle < ActiveRecord::Base
  include HasAuxTable
  belongs_to :vehicle_lot
end

class VehicleLot < ActiveRecord::Base
  has_many :vehicles
end

class Car < Vehicle
  aux_table :car
  has_many :drivers, inverse_of: :car
end

class Boat < Vehicle
  aux_table :boat
  has_many :passengers, inverse_of: :boat
  belongs_to :captain, inverse_of: :boat
end

class Plane < Vehicle
  aux_table :plane
  enum :engine_type, { turbofan: 0, turboprop: 1, piston: 2, electric: 3 }
end

class Person < ActiveRecord::Base
  extend T::Sig
  include HasAuxTable

  validates :name, presence: true, uniqueness: true

  sig do
    params(
      associations: [Symbol, Symbol],
      table_name: Symbol,
      model_class_name: T.any(Symbol, String, T.class_of(ActiveRecord::Base)),
      join_class_name: T.any(Symbol, String, T.class_of(ActiveRecord::Base))
    ).void
  end
  def self.has_and_belongs_to_many_through(
    associations,
    table_name:,
    model_class_name:,
    join_class_name:
  )
    from_assoc_plural, to_assoc_plural = associations
    from_assoc_class_name, to_assoc_class_name =
      model_class_name,
      model_class_name
    from_assoc_singular = from_assoc_plural.to_s.singularize
    from_join_assoc_name = :"#{from_assoc_singular}_#{table_name}"
    to_assoc_singular = to_assoc_plural.to_s.singularize
    to_join_assoc_name = :"#{to_assoc_singular}_#{table_name}"

    has_many(
      from_join_assoc_name,
      primary_key: primary_key,
      foreign_key: "#{from_assoc_singular}_id",
      inverse_of: from_assoc_singular,
      class_name: join_class_name
    )
    has_many(
      to_assoc_plural,
      through: from_join_assoc_name,
      source: to_assoc_singular,
      class_name: to_assoc_class_name
    )

    has_many(
      to_join_assoc_name,
      primary_key: primary_key,
      foreign_key: "#{to_assoc_singular}_id",
      inverse_of: to_assoc_singular,
      class_name: join_class_name
    )
    has_many(
      from_assoc_plural,
      through: to_join_assoc_name,
      source: from_assoc_singular,
      class_name: from_assoc_class_name
    )
  end

  has_and_belongs_to_many_through(
    %i[doctors patients],
    model_class_name: "Person",
    join_class_name: "DoctorPatientJoin",
    table_name: :doctor_patient_joins
  )

  has_and_belongs_to_many_through(
    %i[employers employees],
    model_class_name: "Person",
    join_class_name: "EmployerEmployeeJoin",
    table_name: :employer_employee_joins
  )
end

class Driver < Person
  aux_table :driver
  belongs_to :car, optional: true
  validates :license_number, presence: true, uniqueness: true
end

class Captain < Person
  aux_table :captain
  has_one :boat, inverse_of: :captain
end

class Passenger < Person
  aux_table :passenger
  belongs_to :boat, inverse_of: :passengers
end

module Kitchen
  class Utensil < ActiveRecord::Base
    include HasAuxTable
  end

  class Fork < Utensil
    aux_table :fork
  end

  class Spoon < Utensil
    aux_table :spoon
  end
end

# Non-aux table model that has_and_belongs_to_many w/ counter cache
class Book < ActiveRecord::Base
  has_many :read_book_joins, inverse_of: :book
  has_many :readers, through: :read_book_joins
end

# Aux table model that has_and_belongs_to_many w/ counter cache
class Reader < Person
  aux_table :reader
  has_many :read_book_joins, inverse_of: :reader
  has_many :read_books, through: :read_book_joins, source: :book
end

# The join table for the has_and_belongs_to_many association between Reader and Book
class ReadBookJoin < ActiveRecord::Base
  belongs_to :book, counter_cache: true
  belongs_to :reader, counter_cache: true
end

# Join table that has aux records
class RelationshipJoin < ActiveRecord::Base
  include HasAuxTable
end

class DoctorPatientJoin < RelationshipJoin
  aux_table :doctor_patient
  belongs_to :doctor, class_name: "Person"
  belongs_to :patient, class_name: "Person"
end

class EmployerEmployeeJoin < RelationshipJoin
  aux_table :employer_employee
  belongs_to :employer, class_name: "Person"
  belongs_to :employee, class_name: "Person"
end

class User < ActiveRecord::Base
  include HasAuxTable
  has_many :posts, inverse_of: :user, class_name: "Post"
end

ActiveRecord::Schema.define do
  create_base_table :model_as do |t|
    t.string :a_field1
    t.integer :model_bs_count
    t.integer :ad_joins_count

    t.create_aux :a1 do |t|
      t.integer :a1_field1
      t.integer :model_cs_count
    end

    t.create_aux :a2 do |t|
      t.string :a2_field1
      t.integer :model_b2s_count
    end
  end

  create_base_table :model_bs do |t|
    t.string :b_field1
    t.references :model_a, foreign_key: { to_table: :model_as }

    t.create_aux :b1 do |t|
      t.integer :b1_field1
    end

    t.create_aux :b2 do |t|
      t.references :model_a2,
                   foreign_key: {
                     to_table: :model_as_a2_aux,
                     primary_key: :base_table_id
                   }
    end
  end

  create_table :model_cs do |t|
    t.string :c_field1
    t.references :model_a1,
                 foreign_key: {
                   to_table: :model_as_a1_aux,
                   primary_key: :base_table_id
                 }
  end

  create_base_table :model_ds do |t|
    t.integer :ad_joins_count
    t.create_aux :d1 do |t|
      t.string :d1_field1
    end
  end

  create_table :ad_joins, primary_key: %i[model_a_id model_d_id] do |t|
    t.references :model_a, foreign_key: { to_table: :model_as }
    t.references :model_d, foreign_key: { to_table: :model_ds }
  end

  # A* has_many B
  # A1 has_many C
  # A* has_many ADJoin
  # B belongs_to A
  # C belongs_to A1
  # D* has_many ADJoin
  # ADJoin belongs_to A
  # ADJoin belongs_to D
end

class ModelA < ActiveRecord::Base
  include HasAuxTable
  has_many :model_bs, inverse_of: :model_a
  has_many :ad_joins, inverse_of: :model_a
  has_many :model_ds, through: :ad_joins
  validates :a_field1, presence: true, uniqueness: true
end

class ModelA1 < ModelA
  aux_table :a1
  has_many :model_cs, inverse_of: :model_a1
  validates :a1_field1, presence: true, uniqueness: true
end

class ModelA2 < ModelA
  aux_table :a2
  has_many :model_b2s, inverse_of: :model_a2
  validates :a2_field1, presence: true, uniqueness: true
end

class ModelB < ActiveRecord::Base
  include HasAuxTable
  belongs_to :model_a, inverse_of: :model_bs, counter_cache: true
  validates :b_field1, presence: true, uniqueness: true
end

class ModelB1 < ModelB
  aux_table :b1
  validates :b1_field1, presence: true, uniqueness: true
end

class ModelB2 < ModelB
  aux_table :b2
  belongs_to :model_a2, inverse_of: :model_b2s, counter_cache: true
end

class ModelC < ActiveRecord::Base
  belongs_to :model_a1, inverse_of: :model_cs, counter_cache: true
  validates :c_field1, presence: true, uniqueness: true
end

class ModelD < ActiveRecord::Base
  include HasAuxTable
  has_many :ad_joins, inverse_of: :model_d
  has_many :model_as, through: :ad_joins
end

class ModelD1 < ModelD
  aux_table :d1
  validates :d1_field1, presence: true, uniqueness: true
end

class AdJoin < ActiveRecord::Base
  belongs_to :model_a, inverse_of: :ad_joins, counter_cache: true
  belongs_to :model_d, inverse_of: :ad_joins, counter_cache: true
end

ActiveRecord::Schema.define do
  create_base_table :model_es do |t|
    t.integer :pk_base_id, index: true
    t.integer :fk_base_id, index: true
    t.create_aux :e do |t|
      t.integer :pk_aux_id, index: true
      t.integer :fk_aux_id, index: true
    end
  end
end

class ModelE < ActiveRecord::Base
  include HasAuxTable
end
class ModelECustom < ModelE
  aux_table :e
  belongs_to :base_to_base,
             class_name: "ModelECustom",
             foreign_key: :fk_base_id,
             primary_key: :pk_base_id,
             optional: true

  belongs_to :base_to_aux,
             class_name: "ModelECustom",
             foreign_key: :fk_base_id,
             primary_key: :pk_aux_id,
             optional: true

  belongs_to :aux_to_base,
             class_name: "ModelECustom",
             foreign_key: :fk_aux_id,
             primary_key: :pk_base_id,
             optional: true

  belongs_to :aux_to_aux,
             class_name: "ModelECustom",
             foreign_key: :fk_aux_id,
             primary_key: :pk_aux_id,
             optional: true

  has_one :owned_base_to_base,
          class_name: "ModelECustom",
          primary_key: :pk_base_id,
          foreign_key: :fk_base_id

  has_one :owned_base_to_aux,
          class_name: "ModelECustom",
          primary_key: :pk_base_id,
          foreign_key: :fk_aux_id

  has_one :owned_aux_to_base,
          class_name: "ModelECustom",
          primary_key: :pk_aux_id,
          foreign_key: :fk_base_id

  has_one :owned_aux_to_aux,
          class_name: "ModelECustom",
          primary_key: :pk_aux_id,
          foreign_key: :fk_aux_id

  has_many :owned_base_to_base_many,
           class_name: "ModelECustom",
           primary_key: :pk_base_id,
           foreign_key: :fk_base_id

  has_many :owned_base_to_aux_many,
           class_name: "ModelECustom",
           primary_key: :pk_base_id,
           foreign_key: :fk_aux_id

  has_many :owned_aux_to_base_many,
           class_name: "ModelECustom",
           primary_key: :pk_aux_id,
           foreign_key: :fk_base_id

  has_many :owned_aux_to_aux_many,
           class_name: "ModelECustom",
           primary_key: :pk_aux_id,
           foreign_key: :fk_aux_id
end
