require "rails_helper"

RSpec.describe Validations::Sales::FinancialValidations do
  subject(:financial_validator) { validator_class.new }

  let(:validator_class) { Class.new { include Validations::Sales::FinancialValidations } }

  describe "income validations for shared ownership" do
    let(:record) { FactoryBot.build(:sales_log, ownershipsch: 1) }

    context "when buying in a non london borough" do
      before do
        record.la = "E08000035"
      end

      it "adds errors if buyer 1 has income over 80,000" do
        record.income1 = 85_000
        financial_validator.validate_income1(record)
        expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
        expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
        expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
        expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
      end

      it "adds errors if buyer 2 has income over 80,000" do
        record.income2 = 85_000
        financial_validator.validate_income2(record)
        expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
        expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
        expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
        expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
      end

      it "does not add errors if buyer 1 has income above 0 and below 80_000" do
        record.income1 = 75_000
        financial_validator.validate_income1(record)
        expect(record.errors).to be_empty
      end

      it "does not add errors if buyer 2 has income above 0 and below 80_000" do
        record.income2 = 75_000
        financial_validator.validate_income2(record)
        expect(record.errors).to be_empty
      end

      it "adds errors if buyer 1 has income below 0" do
        record.income1 = -500
        financial_validator.validate_income1(record)
        expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
        expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
        expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
        expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
      end

      it "adds errors if buyer 2 has income below 0" do
        record.income2 = -5
        financial_validator.validate_income2(record)
        expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
        expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
        expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
        expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.outside_non_london_income_range"))
      end

      it "adds errors when combined income is over 80_000" do
        record.income1 = 45_000
        record.income2 = 40_000
        financial_validator.validate_combined_income(record)
        expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.combined_over_hard_max_for_outside_london"))
        expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.combined_over_hard_max_for_outside_london"))
      end

      it "does not add errors when combined income is under 80_000" do
        record.income1 = 35_000
        record.income2 = 40_000
        financial_validator.validate_combined_income(record)
        expect(record.errors).to be_empty
      end
    end

    context "when buying in a london borough" do
      before do
        record.la = "E09000030"
      end

      it "adds errors if buyer 1 has income over 90,000" do
        record.income1 = 95_000
        financial_validator.validate_income1(record)
        expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
        expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
        expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
        expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
      end

      it "adds errors if buyer 2 has income over 90,000" do
        record.income2 = 95_000
        financial_validator.validate_income2(record)
        expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
        expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
        expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
        expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
      end

      it "does not add errors if buyer 1 has income above 0 and below 90_000" do
        record.income1 = 75_000
        financial_validator.validate_income1(record)
        expect(record.errors).to be_empty
      end

      it "does not add errors if buyer 2 has income above 0 and below 90_000" do
        record.income2 = 75_000
        financial_validator.validate_income2(record)
        expect(record.errors).to be_empty
      end

      it "adds errors if buyer 1 has income below 0" do
        record.income1 = -500
        financial_validator.validate_income1(record)
        expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
        expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
        expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
        expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
      end

      it "adds errors if buyer 2 has income below 0" do
        record.income2 = -2
        financial_validator.validate_income2(record)
        expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
        expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
        expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
        expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.outside_london_income_range"))
      end

      it "adds errors when combined income is over 90_000" do
        record.income1 = 55_000
        record.income2 = 40_000
        financial_validator.validate_combined_income(record)
        expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.combined_over_hard_max_for_london"))
        expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.combined_over_hard_max_for_london"))
      end

      it "does not add errors when combined income is under 90_000" do
        record.income1 = 35_000
        record.income2 = 40_000
        financial_validator.validate_combined_income(record)
        expect(record.errors).to be_empty
      end
    end
  end

  describe "#validate_mortgage" do
    let(:record) { FactoryBot.build(:sales_log) }

    it "adds an error is the mortgage is zero" do
      record.mortgageused = 1
      record.mortgage = 0
      financial_validator.validate_mortgage(record)
      expect(record.errors[:mortgage]).to include I18n.t("validations.financial.mortgage")
    end

    it "does not add an error is the mortgage is positive" do
      record.mortgageused = 1
      record.mortgage = 234
      financial_validator.validate_mortgage(record)
      expect(record.errors).to be_empty
    end
  end

  describe "#validate_percentage_bought_not_greater_than_percentage_owned" do
    let(:record) { FactoryBot.build(:sales_log) }

    it "does not add an error if the percentage bought is less than the percentage owned" do
      record.stairbought = 20
      record.stairowned = 40
      financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record)
      expect(record.errors).to be_empty
    end

    it "does not add an error if the percentage bought is equal to the percentage owned" do
      record.stairbought = 30
      record.stairowned = 30
      financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record)
      expect(record.errors).to be_empty
    end

    it "adds an error to stairowned and not stairbought if the percentage bought is more than the percentage owned for joint purchase" do
      record.stairbought = 50
      record.stairowned = 40
      record.jointpur = 1
      financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record)
      expect(record.errors["stairowned"]).to include("Total percentage buyers now own must be more than percentage bought in this transaction.")
    end

    it "adds an error to stairowned and not stairbought if the percentage bought is more than the percentage owned for non joint purchase" do
      record.stairbought = 50
      record.stairowned = 40
      record.jointpur = 2
      financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record)
      expect(record.errors["stairowned"]).to include("Total percentage buyer now owns must be more than percentage bought in this transaction.")
    end
  end

  describe "#validate_percentage_bought_not_equal_percentage_owned" do
    let(:record) { FactoryBot.build(:sales_log) }

    context "with 24/25 logs" do
      before do
        record.saledate = Time.zone.local(2024, 4, 3)
        record.save!(validate: false)
      end

      it "does not add an error if the percentage bought is less than the percentage owned" do
        record.stairbought = 20
        record.stairowned = 40
        financial_validator.validate_percentage_bought_not_equal_percentage_owned(record)
        expect(record.errors).to be_empty
      end

      it "adds an error if the percentage bought is equal to the percentage owned" do
        record.stairbought = 30
        record.stairowned = 30
        financial_validator.validate_percentage_bought_not_equal_percentage_owned(record)
        expect(record.errors["stairowned"]).to include("The percentage bought is 30% and the percentage owned in total is 30%. These figures cannot be the same.")
        expect(record.errors["stairbought"]).to include("The percentage bought is 30% and the percentage owned in total is 30%. These figures cannot be the same.")
      end

      it "does not add an error to stairowned and not stairbought if the percentage bought is more than the percentage owned" do
        record.stairbought = 50
        record.stairowned = 40
        financial_validator.validate_percentage_bought_not_equal_percentage_owned(record)
        expect(record.errors).to be_empty
      end
    end

    context "with 23/24 logs" do
      before do
        record.saledate = Time.zone.local(2023, 4, 3)
        record.save!(validate: false)
      end

      it "does not add an error if the percentage bought is equal to the percentage owned" do
        record.stairbought = 30
        record.stairowned = 30
        financial_validator.validate_percentage_bought_not_equal_percentage_owned(record)
        expect(record.errors).to be_empty
      end
    end
  end

  describe "#validate_monthly_leasehold_charges" do
    let(:record) { FactoryBot.build(:sales_log) }

    it "does not add an error if monthly leasehold charges are positive" do
      record.mscharge = 2345
      financial_validator.validate_monthly_leasehold_charges(record)
      expect(record.errors).to be_empty
    end

    it "adds an error if monthly leasehold charges are zero" do
      record.mscharge = 0
      financial_validator.validate_monthly_leasehold_charges(record)
      expect(record.errors[:mscharge]).to include I18n.t("validations.financial.monthly_leasehold_charges.not_zero")
    end
  end

  describe "#validate_percentage_bought_at_least_threshold" do
    let(:record) { FactoryBot.build(:sales_log) }

    it "adds an error to stairbought and type if the percentage bought is less than the threshold (which is 1% by default, but higher for some shared ownership types)" do
      record.stairbought = 9
      [2, 16, 18, 24].each do |type|
        record.type = type
        financial_validator.validate_percentage_bought_at_least_threshold(record)
        expect(record.errors["stairbought"]).to eq(["The minimum increase in equity while staircasing is 10%."])
        expect(record.errors["type"]).to eq(["The minimum increase in equity while staircasing is 10% for this shared ownership type."])
        record.errors.clear
      end

      record.stairbought = 0
      [28, 30, 31, 32].each do |type|
        record.type = type
        financial_validator.validate_percentage_bought_at_least_threshold(record)
        expect(record.errors["stairbought"]).to eq(["The minimum increase in equity while staircasing is 1%."])
        expect(record.errors["type"]).to eq(["The minimum increase in equity while staircasing is 1% for this shared ownership type."])
        record.errors.clear
      end
    end

    it "doesn't add an error to stairbought and type if the percentage bought is less than the threshold (which is 1% by default, but higher for some shared ownership types)" do
      record.stairbought = 10
      [2, 16, 18, 24].each do |type|
        record.type = type
        financial_validator.validate_percentage_bought_at_least_threshold(record)
        expect(record.errors).to be_empty
        record.errors.clear
      end

      record.stairbought = 1
      [28, 30, 31, 32].each do |type|
        record.type = type
        financial_validator.validate_percentage_bought_at_least_threshold(record)
        expect(record.errors).to be_empty
        record.errors.clear
      end
    end
  end

  describe "#validate_child_income" do
    let(:record) { FactoryBot.build(:sales_log) }

    context "when buyer 2 is not a child" do
      before do
        record.ecstat2 = rand(0..8)
      end

      it "does not add an error if buyer 2 has an income" do
        record.income2 = 40_000
        financial_validator.validate_child_income(record)
        expect(record.errors).to be_empty
      end
    end

    context "when buyer 2 is a child" do
      let(:record) { build(:sales_log, :saledate_today, ecstat2: 9) }

      it "does not add an error if buyer 2 has no income" do
        record.income2 = 0
        financial_validator.validate_child_income(record)
        expect(record.errors).to be_empty
      end

      it "adds errors if buyer 2 has an income" do
        record.income2 = 40_000
        financial_validator.validate_child_income(record)
        expect(record.errors["ecstat2"]).to include(match I18n.t("validations.financial.income.child_has_income"))
        expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.child_has_income"))
      end
    end
  end

  describe "#validate_equity_in_range_for_year_and_type" do
    let(:record) { FactoryBot.build(:sales_log, saledate:, resale: nil) }

    context "with a log in the 22/23 collection year" do
      let(:saledate) { Time.zone.local(2023, 1, 1) }

      it "adds an error for type 2, equity below min with the correct percentage" do
        record.type = 2
        record.equity = 1
        financial_validator.validate_equity_in_range_for_year_and_type(record)
        expect(record.errors["equity"]).to include(match I18n.t("validations.financial.equity.under_min", min_equity: 25))
        expect(record.errors["type"]).to include(match I18n.t("validations.financial.equity.under_min", min_equity: 25))
      end

      it "adds an error for type 30, equity below min with the correct percentage" do
        record.type = 30
        record.equity = 1
        financial_validator.validate_equity_in_range_for_year_and_type(record)
        expect(record.errors["equity"]).to include(match I18n.t("validations.financial.equity.under_min", min_equity: 10))
        expect(record.errors["type"]).to include(match I18n.t("validations.financial.equity.under_min", min_equity: 10))
      end

      it "does not add an error for equity in range with the correct percentage" do
        record.type = 2
        record.equity = 50
        financial_validator.validate_equity_in_range_for_year_and_type(record)
        expect(record.errors).to be_empty
      end

      it "adds an error for equity above max with the correct percentage" do
        record.type = 2
        record.equity = 90
        financial_validator.validate_equity_in_range_for_year_and_type(record)
        expect(record.errors["equity"]).to include(match I18n.t("validations.financial.equity.over_max", max_equity: 75))
        expect(record.errors["type"]).to include(match I18n.t("validations.financial.equity.over_max", max_equity: 75))
      end

      it "does not add an error if it's a resale" do
        record.type = 2
        record.equity = 90
        record.resale = 1
        financial_validator.validate_equity_in_range_for_year_and_type(record)
        expect(record.errors).to be_empty
      end
    end

    context "with a log in 23/24 collection year" do
      let(:saledate) { Time.zone.local(2024, 1, 1) }

      it "adds an error for type 2, equity below min with the correct percentage" do
        record.type = 2
        record.equity = 1
        financial_validator.validate_equity_in_range_for_year_and_type(record)
        expect(record.errors["equity"]).to include(match I18n.t("validations.financial.equity.under_min", min_equity: 25))
        expect(record.errors["type"]).to include(match I18n.t("validations.financial.equity.under_min", min_equity: 25))
      end

      it "adds an error for type 30, equity below min with the correct percentage" do
        record.type = 30
        record.equity = 1
        financial_validator.validate_equity_in_range_for_year_and_type(record)
        expect(record.errors["equity"]).to include(match I18n.t("validations.financial.equity.under_min", min_equity: 10))
        expect(record.errors["type"]).to include(match I18n.t("validations.financial.equity.under_min", min_equity: 10))
      end

      it "does not add an error for equity in range with the correct percentage" do
        record.type = 2
        record.equity = 50
        financial_validator.validate_equity_in_range_for_year_and_type(record)
        expect(record.errors).to be_empty
      end

      it "adds an error for equity above max with the correct percentage" do
        record.type = 2
        record.equity = 90
        financial_validator.validate_equity_in_range_for_year_and_type(record)
        expect(record.errors["equity"]).to include(match I18n.t("validations.financial.equity.over_max", max_equity: 75))
        expect(record.errors["type"]).to include(match I18n.t("validations.financial.equity.over_max", max_equity: 75))
      end
    end
  end

  describe "#validate_equity_less_than_staircase_difference" do
    let(:record) { FactoryBot.build(:sales_log, saledate:) }

    context "with a log in the 23/24 collection year" do
      let(:saledate) { Time.zone.local(2023, 4, 1) }

      it "does not add an error" do
        record.stairbought = 2
        record.stairowned = 3
        record.equity = 2
        financial_validator.validate_equity_less_than_staircase_difference(record)
        expect(record.errors).to be_empty
      end
    end

    context "with a log in 24/25 collection year" do
      let(:saledate) { Time.zone.local(2024, 4, 1) }

      it "adds errors if equity is more than stairowned - stairbought for joint purchase" do
        record.stairbought = 2
        record.stairowned = 3
        record.equity = 2
        record.jointpur = 1
        financial_validator.validate_equity_less_than_staircase_difference(record)
        expect(record.errors["equity"]).to include("The initial equity stake is 2% and the percentage owned in total minus the percentage bought is 1%. In a staircasing transaction, the equity stake purchased cannot be larger than the percentage the buyers own minus the percentage bought.")
        expect(record.errors["stairowned"]).to include("The initial equity stake is 2% and the percentage owned in total minus the percentage bought is 1%. In a staircasing transaction, the equity stake purchased cannot be larger than the percentage the buyers own minus the percentage bought.")
        expect(record.errors["stairbought"]).to include("The initial equity stake is 2% and the percentage owned in total minus the percentage bought is 1%. In a staircasing transaction, the equity stake purchased cannot be larger than the percentage the buyers own minus the percentage bought.")
      end

      it "adds errors if equity is more than stairowned - stairbought for non joint purchase" do
        record.stairbought = 2
        record.stairowned = 3
        record.equity = 2
        record.jointpur = 2
        financial_validator.validate_equity_less_than_staircase_difference(record)
        expect(record.errors["equity"]).to include("The initial equity stake is 2% and the percentage owned in total minus the percentage bought is 1%. In a staircasing transaction, the equity stake purchased cannot be larger than the percentage the buyer owns minus the percentage bought.")
        expect(record.errors["stairowned"]).to include("The initial equity stake is 2% and the percentage owned in total minus the percentage bought is 1%. In a staircasing transaction, the equity stake purchased cannot be larger than the percentage the buyer owns minus the percentage bought.")
        expect(record.errors["stairbought"]).to include("The initial equity stake is 2% and the percentage owned in total minus the percentage bought is 1%. In a staircasing transaction, the equity stake purchased cannot be larger than the percentage the buyer owns minus the percentage bought.")
      end

      it "does not add errors if equity is less than stairowned - stairbought" do
        record.stairbought = 2
        record.stairowned = 10
        record.equity = 2
        financial_validator.validate_equity_less_than_staircase_difference(record)
        expect(record.errors).to be_empty
      end

      it "does not add errors if equity is equal stairowned - stairbought" do
        record.stairbought = 2
        record.stairowned = 10
        record.equity = 8
        financial_validator.validate_equity_less_than_staircase_difference(record)
        expect(record.errors).to be_empty
      end

      it "does not add errors if stairbought is not given" do
        record.stairbought = nil
        record.stairowned = 10
        record.equity = 2
        financial_validator.validate_equity_less_than_staircase_difference(record)
        expect(record.errors).to be_empty
      end

      it "does not add errors if stairowned is not given" do
        record.stairbought = 2
        record.stairowned = nil
        record.equity = 2
        financial_validator.validate_equity_less_than_staircase_difference(record)
        expect(record.errors).to be_empty
      end

      it "does not add errors if equity is not given" do
        record.stairbought = 2
        record.stairowned = 10
        record.equity = 0
        financial_validator.validate_equity_less_than_staircase_difference(record)
        expect(record.errors).to be_empty
      end
    end
  end
end