# Copyright 2020 Google LLC
#
# Use of this source code is governed by an MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT.

# frozen_string_literal: true

require "test_helper"

module ActiveRecord
  class Migration
    class ReferencesForeignKeyInCreateTest < SpannerAdapter::TestCase
      include SpannerAdapter::Migration::TestHelper

      def setup
        skip_test_table_create!
        super
        connection.ddl_batch do
          @connection.create_table(:testing_parents, force: true)
        end
      end

      def teardown
        @connection.ddl_batch do
          @connection.drop_table "testings", if_exists: true
          @connection.drop_table "testing_parents", if_exists: true
        end
      end

      def test_create_foreign_key_with_table_create
        connection.ddl_batch do
          connection.create_table :testings do |t|
            t.references :testing_parent, foreign_key: true
          end
        end

        fk = connection.foreign_keys("testings").first
        assert_equal "testings", fk.from_table
        assert_equal "testing_parents", fk.to_table
      end

      def test_create_foreign_key_created_by_default
        connection.ddl_batch do
          connection.create_table :testings do |t|
            t.references :testing_parent
          end
        end

        assert_equal [], @connection.foreign_keys("testings")
      end

      def test_options_can_be_passed
        connection.ddl_batch do
          connection.change_table :testing_parents do |t|
            t.references :other, index: { unique: true }
          end
          connection.create_table :testings do |t|
            t.references :testing_parent, foreign_key: { primary_key: :other_id }
          end
        end

        fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" }
        assert_equal "other_id", fk.primary_key
      end

      def test_to_table_options_can_be_passed
        connection.ddl_batch do
          @connection.create_table :testings do |t|
            t.references :parent, foreign_key: { to_table: :testing_parents }
          end
        end
        fks = @connection.foreign_keys("testings")
        assert_equal([["testings", "testing_parents", "parent_id"]],
                      fks.map { |fk| [fk.from_table, fk.to_table, fk.column] })
      end
    end
  end
end

module ActiveRecord
  class Migration
    class ReferencesForeignKeyTest < SpannerAdapter::TestCase
      include SpannerAdapter::Migration::TestHelper
      include ActiveSupport::Testing::Stream

      def setup
        skip_test_table_create!
        super
        connection = ActiveRecord::Base.connection
        connection.ddl_batch do
          connection.create_table(:testing_parents, force: true)
        end
      end

      def teardown
        connection.ddl_batch do
          connection.drop_table "testings", if_exists: true
          connection.drop_table "testing_parents", if_exists: true
        end
      end

      def test_create_foreign_key_with_changing_table
        connection.ddl_batch do
          connection.create_table :testings
          connection.change_table :testings do |t|
            t.references :testing_parent, foreign_key: true
          end
        end

        fk = connection.foreign_keys("testings").first
        assert_equal "testings", fk.from_table
        assert_equal "testing_parents", fk.to_table
      end

      def test_foreign_key_not_added_by_default_on_table_change
        connection.create_table :testings
        connection.change_table :testings do |t|
          t.references :testing_parent
        end

        assert_equal [], connection.foreign_keys("testings")
      end

      def test_foreign_key_accept_option_on_table_change
        connection.ddl_batch do
          connection.change_table :testing_parents do |t|
            t.references :other, index: { unique: true }
          end
          connection.create_table :testings
        end
        connection.change_table :testings do |t|
          t.references :testing_parent, foreign_key: { primary_key: :other_id }
        end

        fk = connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" }
        assert_equal "other_id", fk.primary_key
      end

      def test_foreign_key_column_can_be_removed
        connection.ddl_batch do
          connection.create_table :testings do |t|
            t.references :testing_parent, index: true, foreign_key: true
          end
        end

        connection.remove_reference :testings, :testing_parent, foreign_key: true
        assert_equal 0, connection.foreign_keys('testings').size
      end

      def test_remove_column_removes_foreign_key
        connection.ddl_batch do
          connection.create_table :testings do |t|
            t.references :testing_parent, index: true, foreign_key: true
          end
        end

        connection.ddl_batch do
          connection.remove_column :testings, :testing_parent_id
        end
        assert_equal 0, connection.foreign_keys('testings').size
      end

      def test_foreign_key_respect_pluralize_table_names
        original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names
        ActiveRecord::Base.pluralize_table_names = false
        connection.create_table :testing
        connection.ddl_batch do
          connection.change_table :testing_parents do |t|
            t.references :testing, foreign_key: true
          end
        end

        fk = connection.foreign_keys("testing_parents").first
        assert_equal "testing_parents", fk.from_table
        assert_equal "testing", fk.to_table

        assert_difference "@connection.foreign_keys('testing_parents').size", -1 do
          connection.remove_reference :testing_parents, :testing, foreign_key: true
        end
      ensure
        ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names
        connection.ddl_batch do
          connection.drop_table "testing", if_exists: true
        end
      end

      class CreateDogsMigration < ActiveRecord::Migration::Current
        def up
          connection.ddl_batch do
            create_table :dog_owners

            create_table :dogs do |t|
              t.references :dog_owner, foreign_key: true
            end
          end
        end

        def down
          connection.ddl_batch do
            drop_table :dogs, if_exists: true
            drop_table :dog_owners, if_exists: true
          end
        end
      end

      def test_references_foreign_key_with_prefix
        ActiveRecord::Base.table_name_prefix = "p_"
        migration = CreateDogsMigration.new
        silence_stream($stdout) { migration.migrate(:up) }
        assert_equal 1, @connection.foreign_keys("p_dogs").size
      ensure
        silence_stream($stdout) { migration.migrate(:down) }
        ActiveRecord::Base.table_name_prefix = nil
      end

      def test_references_foreign_key_with_suffix
        ActiveRecord::Base.table_name_suffix = "_s"
        migration = CreateDogsMigration.new
        silence_stream($stdout) { migration.migrate(:up) }
        assert_equal 1, @connection.foreign_keys("dogs_s").size
      ensure
        silence_stream($stdout) { migration.migrate(:down) }
        ActiveRecord::Base.table_name_suffix = nil
      end

      def test_multiple_foreign_keys_can_added_to_same_table
        connection.ddl_batch do
          connection.create_table :testings do |t|
            t.references :parent1, foreign_key: { to_table: :testing_parents }
            t.references :parent2, foreign_key: { to_table: :testing_parents }
          end
        end

        fks = connection.foreign_keys("testings").sort_by(&:column)

        fk_definitions = fks.map { |fk| [fk.from_table, fk.to_table, fk.column] }
        assert_equal([["testings", "testing_parents", "parent1_id"],
                      ["testings", "testing_parents", "parent2_id"]], fk_definitions)
      end

      def test_multiple_foreign_keys_removed
        connection.create_table :testings do |t|
          t.references :parent1, foreign_key: { to_table: :testing_parents }
          t.references :parent2, foreign_key: { to_table: :testing_parents }
        end

        connection.remove_reference :testings, :parent1, foreign_key: { to_table: :testing_parents }

        assert_equal 1, connection.foreign_keys('testings').size

        fks = connection.foreign_keys("testings").sort_by(&:column)

        fk_definitions = fks.map { |fk| [fk.from_table, fk.to_table, fk.column] }
        assert_equal([["testings", "testing_parents", "parent2_id"]], fk_definitions)
      end
    end
  end
end
