# frozen_string_literal: true

require "cases/migration/helper"

module ActiveRecord
  class Migration
    class ColumnAttributesTest < ActiveRecord::TestCase
      include ActiveRecord::Migration::TestHelper

      self.use_transactional_tests = false

      def test_add_column_newline_default
        string = "foo\nbar"
        add_column "test_models", "command", :string, default: string
        TestModel.reset_column_information

        assert_equal string, TestModel.new.command
      end

      def test_add_remove_single_field_using_string_arguments
        assert_no_column TestModel, :last_name

        add_column "test_models", "last_name", :string
        assert_column TestModel, :last_name

        remove_column "test_models", "last_name"
        assert_no_column TestModel, :last_name
      end

      def test_add_remove_single_field_using_symbol_arguments
        assert_no_column TestModel, :last_name

        add_column :test_models, :last_name, :string
        assert_column TestModel, :last_name

        remove_column :test_models, :last_name
        assert_no_column TestModel, :last_name
      end

      def test_add_column_without_limit
        # TODO: limit: nil should work with all adapters.
        skip "MySQL wrongly enforces a limit of 255" if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
        add_column :test_models, :description, :string, limit: nil
        TestModel.reset_column_information
        assert_nil TestModel.columns_hash["description"].limit
      end

      if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter)
        def test_unabstracted_database_dependent_types
          add_column :test_models, :intelligence_quotient, :smallint
          TestModel.reset_column_information
          assert_match(/smallint/, TestModel.columns_hash["intelligence_quotient"].sql_type)
        end
      end

      unless current_adapter?(:SQLite3Adapter)
        # We specifically do a manual INSERT here, and then test only the SELECT
        # functionality. This allows us to more easily catch INSERT being broken,
        # but SELECT actually working fine.
        def test_native_decimal_insert_manual_vs_automatic
          correct_value = "0012345678901234567890.0123456789".to_d

          connection.add_column "test_models", "wealth", :decimal, precision: "30", scale: "10"

          connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)"

          # SELECT
          row = TestModel.first
          assert_kind_of BigDecimal, row.wealth

          # If this assert fails, that means the SELECT is broken!
          assert_equal correct_value, row.wealth

          # Reset to old state
          TestModel.delete_all

          # Now use the Rails insertion
          TestModel.create wealth: BigDecimal("12345678901234567890.0123456789")

          # SELECT
          row = TestModel.first
          assert_kind_of BigDecimal, row.wealth

          # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
          assert_equal correct_value, row.wealth
        end
      end

      def test_add_column_with_precision_and_scale
        connection.add_column "test_models", "wealth", :decimal, precision: 9, scale: 7

        wealth_column = TestModel.columns_hash["wealth"]
        assert_equal 9, wealth_column.precision
        assert_equal 7, wealth_column.scale
      end

      # Test SQLite3 adapter specifically for decimal types with precision and scale
      # attributes, since these need to be maintained in schema but aren't actually
      # used in SQLite3 itself
      if current_adapter?(:SQLite3Adapter)
        def test_change_column_with_new_precision_and_scale
          connection.add_column "test_models", "wealth", :decimal, precision: 9, scale: 7

          connection.change_column "test_models", "wealth", :decimal, precision: 12, scale: 8
          TestModel.reset_column_information

          wealth_column = TestModel.columns_hash["wealth"]
          assert_equal 12, wealth_column.precision
          assert_equal 8, wealth_column.scale
        end

        def test_change_column_preserve_other_column_precision_and_scale
          connection.add_column "test_models", "last_name", :string
          connection.add_column "test_models", "wealth", :decimal, precision: 9, scale: 7

          wealth_column = TestModel.columns_hash["wealth"]
          assert_equal 9, wealth_column.precision
          assert_equal 7, wealth_column.scale

          connection.change_column "test_models", "last_name", :string, null: false
          TestModel.reset_column_information

          wealth_column = TestModel.columns_hash["wealth"]
          assert_equal 9, wealth_column.precision
          assert_equal 7, wealth_column.scale
        end
      end

      unless current_adapter?(:SQLite3Adapter)
        def test_native_types
          add_column "test_models", "first_name", :string
          add_column "test_models", "last_name", :string
          add_column "test_models", "bio", :text
          add_column "test_models", "age", :integer
          add_column "test_models", "height", :float
          add_column "test_models", "wealth", :decimal, precision: "30", scale: "10"
          add_column "test_models", "birthday", :datetime
          add_column "test_models", "favorite_day", :date
          add_column "test_models", "moment_of_truth", :datetime
          add_column "test_models", "male", :boolean

          TestModel.create first_name: "bob", last_name: "bobsen",
            bio: "I was born ....", age: 18, height: 1.78,
            wealth: BigDecimal("12345678901234567890.0123456789"),
            birthday: 18.years.ago, favorite_day: 10.days.ago,
            moment_of_truth: "1782-10-10 21:40:18", male: true

          bob = TestModel.first
          assert_equal "bob", bob.first_name
          assert_equal "bobsen", bob.last_name
          assert_equal "I was born ....", bob.bio
          assert_equal 18, bob.age

          # Test for 30 significant digits (beyond the 16 of float), 10 of them
          # after the decimal place.

          assert_equal BigDecimal("0012345678901234567890.0123456789"), bob.wealth

          assert_equal true, bob.male?

          assert_equal String, bob.first_name.class
          assert_equal String, bob.last_name.class
          assert_equal String, bob.bio.class
          assert_kind_of Integer, bob.age
          assert_equal Time, bob.birthday.class
          assert_equal Date, bob.favorite_day.class
          assert_instance_of TrueClass, bob.male?
          assert_kind_of BigDecimal, bob.wealth
        end
      end

      if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter)
        def test_out_of_range_limit_should_raise
          assert_raise(ArgumentError) { add_column :test_models, :integer_too_big, :integer, limit: 10 }
          assert_raise(ArgumentError) { add_column :test_models, :text_too_big, :text, limit: 0xfffffffff }
          assert_raise(ArgumentError) { add_column :test_models, :binary_too_big, :binary, limit: 0xfffffffff }
        end
      end
    end
  end
end
