class SalesLogValidator < ActiveModel::Validator
  include Validations::Sales::SetupValidations
  include Validations::Sales::HouseholdValidations
  include Validations::Sales::PropertyValidations
  include Validations::Sales::FinancialValidations
  include Validations::Sales::SaleInformationValidations
  include Validations::SharedValidations
  include Validations::LocalAuthorityValidations

  def validate(record)
    validation_methods = public_methods.select { |method| method.starts_with?("validate_") }
    validation_methods.each { |meth| public_send(meth, record) }
  end
end

class SalesLog < Log
  include DerivedVariables::SalesLogVariables
  include Validations::Sales::SoftValidations
  include Validations::SoftValidations
  include MoneyFormattingHelper

  self.inheritance_column = :_type_disabled

  has_paper_trail

  validates_with SalesLogValidator
  before_validation :recalculate_start_year!, if: :saledate_changed?
  before_validation :process_postcode_changes!, if: :postcode_full_changed?
  before_validation :process_previous_postcode_changes!, if: :ppostcode_full_changed?
  before_validation :reset_invalidated_dependent_fields!
  before_validation :reset_location_fields!, unless: :postcode_known?
  before_validation :reset_previous_location_fields!, unless: :previous_postcode_known?
  before_validation :set_derived_fields!
  before_validation :process_uprn_change!, if: :should_process_uprn_change?
  before_validation :process_address_change!, if: :should_process_address_change?

  belongs_to :managing_organisation, class_name: "Organisation", optional: true

  scope :filter_by_year, ->(year) { where(saledate: Time.zone.local(year.to_i, 4, 1)...Time.zone.local(year.to_i + 1, 4, 1)) }
  scope :filter_by_years_or_nil, lambda { |years, _user = nil|
    first_year = years.shift
    query = filter_by_year(first_year)
    years.each { |year| query = query.or(filter_by_year(year)) }
    query = query.or(where(saledate: nil))
    query.all
  }
  scope :filter_by_purchaser_code, ->(purchid) { where("purchid ILIKE ?", "%#{purchid}%") }
  scope :search_by, lambda { |param|
    by_id = Arel.sql("CASE WHEN id = #{param.to_i} THEN 0 ELSE 1 END")
    by_purchaser_code = Arel.sql("CASE WHEN purchid = '#{param}' THEN 0 WHEN purchid ILIKE '%#{param}%' THEN 1 ELSE 2 END")
    by_postcode = Arel.sql("CASE WHEN REPLACE(postcode_full, ' ', '') = '#{param.delete(' ')}' THEN 0 WHEN REPLACE(postcode_full, ' ', '') ILIKE '%#{param.delete(' ')}%' THEN 1 ELSE 2 END")

    filter_by_purchaser_code(param)
      .or(filter_by_postcode(param))
      .or(filter_by_id(param.gsub(/log/i, "")))
      .order(by_id, by_purchaser_code, by_postcode)
  }
  scope :age1_answered, -> { where.not(age1: nil).or(where(age1_known: [1, 2])) }
  scope :duplicate_logs, lambda { |log|
    visible.where(log.slice(*DUPLICATE_LOG_ATTRIBUTES))
    .where.not(id: log.id)
    .where.not(saledate: nil)
    .where.not(sex1: nil)
    .where.not(ecstat1: nil)
    .where.not(postcode_full: nil)
    .age1_answered
  }
  scope :after_date, ->(date) { where("saledate >= ?", date) }

  scope :duplicate_sets, lambda { |assigned_to_id = nil|
    scope = visible
    .group(*DUPLICATE_LOG_ATTRIBUTES)
    .where.not(saledate: nil)
    .where.not(sex1: nil)
    .where.not(ecstat1: nil)
    .where.not(postcode_full: nil)
    .age1_answered
    .having("COUNT(*) > 1")

    if assigned_to_id
      scope = scope.having("MAX(CASE WHEN assigned_to_id = ? THEN 1 ELSE 0 END) >= 1", assigned_to_id)
    end

    scope.pluck("ARRAY_AGG(id)")
  }

  OPTIONAL_FIELDS = %w[purchid othtype buyers_organisations].freeze
  DUPLICATE_LOG_ATTRIBUTES = %w[owning_organisation_id purchid saledate age1_known age1 sex1 ecstat1 postcode_full].freeze

  def lettings?
    false
  end

  def sales?
    true
  end

  def startdate
    saledate
  end

  def self.editable_fields
    attribute_names
  end

  def purchaser_code
    purchid
  end

  def form_name
    return unless saledate

    FormHandler.instance.form_name_from_start_year(collection_start_year, "sales")
  end

  def form
    FormHandler.instance.get_form(form_name) || FormHandler.instance.current_sales_form
  end

  def optional_fields
    OPTIONAL_FIELDS + dynamically_not_required
  end

  def dynamically_not_required
    not_required = []
    not_required << "proplen" if proplen_optional?
    not_required << "mortlen" if mortlen_optional?
    not_required << "frombeds" if frombeds_optional?
    not_required << "deposit" if form.start_year_after_2024? && stairowned_100?

    not_required |= %w[address_line2 county postcode_full] if saledate && collection_start_year_for_date(saledate) >= 2023

    not_required
  end

  def proplen_optional?
    return false unless collection_start_year

    collection_start_year < 2023
  end

  def mortlen_optional?
    return false unless collection_start_year

    collection_start_year < 2023
  end

  def frombeds_optional?
    return false unless collection_start_year

    collection_start_year < 2023
  end

  def unresolved
    false
  end

  LONDON_BOROUGHS = %w[
    E09000001
    E09000033
    E09000020
    E09000013
    E09000032
    E09000022
    E09000028
    E09000030
    E09000012
    E09000019
    E09000007
    E09000005
    E09000009
    E09000018
    E09000027
    E09000021
    E09000024
    E09000029
    E09000008
    E09000006
    E09000023
    E09000011
    E09000004
    E09000016
    E09000002
    E09000026
    E09000025
    E09000031
    E09000014
    E09000010
    E09000003
    E09000015
    E09000017
  ].freeze

  def london_property?
    la && LONDON_BOROUGHS.include?(la)
  end

  def property_not_in_london?
    !london_property?
  end

  def income1_used_for_mortgage?
    inc1mort == 1
  end

  def buyers_will_live_in?
    buylivein == 1
  end

  def buyers_will_not_live_in?
    buylivein == 2
  end

  def buyer_two_will_live_in_property?
    buy2livein == 1
  end

  def buyer_two_will_not_live_in_property?
    buy2livein == 2
  end

  def buyer_one_will_not_live_in_property?
    buy1livein == 2
  end

  def buyer_two_not_already_living_in_property?
    buy2living == 2
  end

  def income2_used_for_mortgage?
    inc2mort == 1
  end

  def right_to_buy?
    [9, 14, 27].include?(type)
  end

  def rent_to_buy_full_ownership?
    type == 29
  end

  def outright_sale_or_discounted_with_full_ownership?
    ownershipsch == 3 || (ownershipsch == 2 && rent_to_buy_full_ownership?)
  end

  def social_homebuy?
    type == 18
  end

  def ppostcode_full=(postcode)
    if postcode
      super UKPostcode.parse(postcode).to_s
    else
      super nil
    end
  end

  def previous_postcode_known?
    ppcodenk&.zero?
  end

  def postcode_known?
    pcodenk&.zero?
  end

  def postcode_full=(postcode)
    if postcode
      super UKPostcode.parse(postcode).to_s
    else
      super nil
    end
  end

  def expected_shared_ownership_deposit_value
    return unless value && equity

    value * equity / 100
  end

  def stairbought_part_of_value
    return unless value && stairbought

    value * stairbought / 100
  end

  def mortgage_deposit_and_discount_total
    mortgage_amount = mortgage || 0
    deposit_amount = deposit || 0
    cashdis_amount = cashdis || 0

    mortgage_amount + deposit_amount + cashdis_amount
  end

  def deposit_and_discount_total
    deposit_amount = deposit || 0
    cashdis_amount = cashdis || 0

    deposit_amount + cashdis_amount
  end

  def value_times_equity
    return unless value && equity

    value * equity / 100
  end

  def mortgage_deposit_and_discount_error_fields
    [
      "mortgage",
      "deposit",
      cashdis.present? ? "cash discount" : nil,
    ].compact.to_sentence
  end

  def mortgage_and_deposit_total
    return unless mortgage && deposit

    mortgage + deposit
  end

  def outright_sale?
    ownershipsch == 3
  end

  def discounted_ownership_sale?
    ownershipsch == 2
  end

  def mortgage_used?
    mortgageused == 1
  end

  def mortgage_not_used?
    mortgageused == 2
  end

  def mortgage_use_unknown?
    mortgageused == 3
  end

  def process_postcode_changes!
    self.postcode_full = upcase_and_remove_whitespace(postcode_full)
    return if postcode_full.blank?

    self.pcodenk = 0
    inferred_la = get_inferred_la(postcode_full)
    self.is_la_inferred = inferred_la.present?
    self.la = inferred_la if inferred_la.present?
  end

  def process_previous_postcode_changes!
    self.ppostcode_full = upcase_and_remove_whitespace(ppostcode_full)
    return if ppostcode_full.blank?

    self.ppcodenk = 0
    inferred_la = get_inferred_la(ppostcode_full)
    self.is_previous_la_inferred = inferred_la.present?
    self.prevloc = inferred_la if inferred_la.present?
  end

  def reset_assigned_to!
    return unless updated_by&.support?
    return if owning_organisation.blank? || managing_organisation.blank? || assigned_to.blank?
    return if assigned_to&.organisation == owning_organisation || assigned_to&.organisation == managing_organisation
    return if assigned_to&.organisation == owning_organisation.absorbing_organisation || assigned_to&.organisation == managing_organisation.absorbing_organisation

    update!(assigned_to: nil)
  end

  def joint_purchase?
    jointpur == 1
  end

  def not_joint_purchase?
    jointpur == 2
  end

  def buyer_has_seen_privacy_notice?
    privacynotice == 1
  end

  def buyer_not_interviewed?
    noint == 1
  end

  def old_persons_shared_ownership?
    type == 24
  end

  def is_bedsit?
    proptype == 2
  end

  def shared_ownership_scheme?
    ownershipsch == 1
  end

  def company_buyer?
    companybuy == 1
  end

  def no_monthly_leasehold_charges?
    has_mscharge&.zero?
  end

  def no_buyer_organisation?
    pregyrha&.zero? &&
      pregla&.zero? &&
      pregghb&.zero? &&
      pregother&.zero?
  end

  def buyers_age_for_old_persons_shared_ownership_invalid?
    return unless old_persons_shared_ownership?

    (joint_purchase? && ages_unknown_or_under_64?([1, 2])) || (not_joint_purchase? && ages_unknown_or_under_64?([1]))
  end

  def ages_unknown_or_under_64?(person_indexes)
    person_indexes.all? { |person_num| self["age#{person_num}"].present? && self["age#{person_num}"] < 64 || self["age#{person_num}_known"] == 1 }
  end

  def purchase_price_soft_min
    LaSaleRange.find_by(start_year: collection_start_year, la:, bedrooms: beds).soft_min
  end

  def purchase_price_soft_max
    LaSaleRange.find_by(start_year: collection_start_year, la:, bedrooms: beds).soft_max
  end

  def income_soft_min_for_ecstat(ecstat_field)
    economic_status_code = public_send(ecstat_field)

    return unless ALLOWED_INCOME_RANGES_SALES

    soft_min = ALLOWED_INCOME_RANGES_SALES[economic_status_code]&.soft_min
    format_as_currency(soft_min)
  end

  def should_process_uprn_change?
    return unless uprn
    return unless saledate
    return unless collection_start_year_for_date(saledate) >= 2023

    uprn_changed? || saledate_changed?
  end

  def should_process_address_change?
    return unless uprn_selection || select_best_address_match
    return unless saledate
    return unless form.start_year_after_2024?

    if select_best_address_match
      address_line1_input.present? && postcode_full_input.present?
    else
      uprn_selection_changed? || saledate_changed?
    end
  end

  def value_with_discount
    return if value.blank?

    discount_amount = discount ? value * discount / 100 : 0
    value - discount_amount
  end

  def mortgage_deposit_and_grant_total
    return if deposit.blank?

    grant_amount = grant || 0
    mortgage_amount = mortgage || 0
    mortgage_amount + deposit + grant_amount
  end

  def beds_for_la_sale_range
    beds.nil? ? nil : [beds, LaSaleRange::MAX_BEDS].min
  end

  def ownership_scheme(uppercase: false)
    ownership_scheme = case ownershipsch
                       when 1 then "shared ownership"
                       when 2 then "discounted ownership"
                       when 3 then "outright or other sale"
                       end
    uppercase ? ownership_scheme.capitalize : ownership_scheme
  end

  def combined_income
    buyer_1_income = income1 || 0
    buyer_2_income = income2 || 0

    buyer_1_income + buyer_2_income
  end

  def blank_compound_invalid_non_setup_fields!
    super

    self.pcodenk = nil if errors.attribute_names.include? :postcode_full
  end

  def duplicate_check_question_ids
    ["owning_organisation_id",
     "saledate",
     "purchid",
     "age1",
     "sex1",
     "ecstat1",
     form.start_date.year < 2023 || uprn.blank? ? "postcode_full" : nil,
     form.start_date.year >= 2023 && uprn.present? ? "uprn" : nil].compact
  end

  def soctenant_is_inferred?
    form.start_year_after_2024?
  end

  def duplicates
    SalesLog.where.not(duplicate_set_id: nil).where(duplicate_set_id:).where.not(id:)
  end

  def nationality2_uk_or_prefers_not_to_say?
    nationality_all_buyer2_group&.zero? || nationality_all_buyer2_group == 826
  end

  def is_staircase?
    staircase == 1
  end

  def discount_value
    return unless discount && value

    value * discount / 100
  end

  def is_not_staircasing?
    staircase == 2 || staircase == 3
  end

  def stairowned_100?
    stairowned == 100
  end

  def address_search_given?
    address_line1_input.present? && postcode_full_input.present?
  end
end