require "rails_helper"

RSpec.describe Form::Question, type: :model do
  subject(:question) { described_class.new(question_id, question_definition, page) }

  let(:lettings_log) { FactoryBot.build(:lettings_log) }
  let(:type) { "numeric" }
  let(:readonly) { nil }
  let(:prefix) { nil }
  let(:suffix) { nil }
  let(:depends_on_met) { nil }
  let(:conditional_question_conditions) { nil }
  let(:form_questions) { nil }
  let(:answer_options) { { "1" => { "value" => "Weekly" }, "2" => { "value" => "Monthly" } } }
  let(:inferred_check_answers_value) { [{ "condition" => { "postcode_known" => 0 }, "value" => "Weekly" }] }

  let(:form) { instance_double(Form, depends_on_met:, conditional_question_conditions:) }
  let(:subsection) { instance_double(Form::Subsection, form:) }
  let(:page) { instance_double(Form::Page, subsection:, routed_to?: true, questions: form_questions) }
  let(:question_id) { "earnings" }
  let(:question_definition) do
    { "header" => "What is the tenant’s /and partner’s combined income after tax?",
      "check_answer_label" => "Income",
      "type" => type,
      "min" => 0,
      "step" => 1,
      "answer_options" => answer_options,
      "readonly" => readonly,
      "result-field" => "tcharge",
      "fields-to-add" => %w[brent scharge pscharge supcharg],
      "inferred_check_answers_value" => inferred_check_answers_value,
      "suffix" => suffix,
      "prefix" => prefix,
      "hidden_in_check_answers" => {} }
  end

  it "has an id" do
    expect(question.id).to eq(question_id)
  end

  it "has a header" do
    expect(question.header).to eq("What is the tenant’s /and partner’s combined income after tax?")
  end

  it "has a check answers label" do
    expect(question.check_answer_label).to eq("Income")
  end

  it "has a question type" do
    expect(question.type).to eq("numeric")
  end

  it "belongs to a page" do
    expect(question.page).to eq(page)
  end

  it "belongs to a subsection" do
    expect(question.subsection).to eq(subsection)
  end

  it "has a read only helper" do
    expect(question.read_only?).to be false
  end

  it "has a yes value helper" do
    expect(question).to be_value_is_yes("Yes")
    expect(question).to be_value_is_yes("YES")
    expect(question).not_to be_value_is_yes("random")
  end

  context "when type is numeric" do
    it "has a min value" do
      expect(question.min).to eq(0)
    end

    it "has a step value" do
      expect(question.step).to eq(1)
    end

    it "does not map value from label" do
      expect(question.value_from_label("5")).to eq("5")
    end
  end

  context "when type is radio" do
    let(:type) { "radio" }

    it "has answer options" do
      expected_answer_options = { "1" => { "value" => "Weekly" }, "2" => { "value" => "Monthly" } }
      expect(question.answer_options).to eq(expected_answer_options)
    end

    it "can map value from label" do
      expect(question.value_from_label("Monthly")).to eq("2")
    end

    it "can map label from value" do
      expect(question.label_from_value(1)).to eq("Weekly")
    end

    context "when answer options include yes, no, prefer not to say" do
      let(:question_id) { "illness" }

      it "maps those options" do
        expect(question).to be_value_is_yes(1)
        expect(question).not_to be_value_is_refused(1)
        expect(question).to be_value_is_dont_know(3)
      end
    end

    context "when answer options includes don’t know" do
      let(:question_id) { "layear" }

      it "maps those options" do
        expect(question).not_to be_value_is_yes(7)
        expect(question).not_to be_value_is_refused(7)
        expect(question).to be_value_is_dont_know(7)
      end
    end

    context "when answer options do not include derived options" do
      it "displays all answer options" do
        expect(question.displayed_answer_options(lettings_log)).to match(question.answer_options)
      end
    end

    context "when answer options include derived options" do
      let(:answer_options) { { "0" => { "value" => "Other" }, "9" => { "value" => "This", "depends_on" => [false] } } }
      let(:depends_on_met) { false }
      let(:expected_answer_options) do
        {
          "0" => { "value" => "Other" },
        }
      end

      it "does not include those options in the displayed options" do
        expect(question.displayed_answer_options(lettings_log)).to match(expected_answer_options)
      end

      it "can still map the value label" do
        expect(question.label_from_value(9)).to eq("This")
      end
    end

    context "when the saved answer is not in the value map" do
      it "displays the saved answer umapped" do
        expect(question.label_from_value(9999)).to eq("9999")
      end
    end
  end

  context "when type is select" do
    let(:type) { "select" }
    let(:answer_options) { { "E08000003" => "Manchester", "E09000033" => "Westminster" } }

    it "can map value from label" do
      expect(question.value_from_label("Manchester")).to eq("E08000003")
    end

    it "can map label from value" do
      expect(question.label_from_value("E09000033")).to eq("Westminster")
    end

    context "when the saved answer is not in the value map" do
      it "displays the saved answer umapped" do
        expect(question.label_from_value(9999)).to eq("9999")
      end
    end
  end

  context "when type is checkbox" do
    let(:type) { "checkbox" }
    let(:page_id) { "illness" }
    let(:answer_options) { { "illness_type_1" => { "value" => "Vision - such as blindness or partial sight" }, "illness_type_2" => { "value" => "Hearing - such as deafness or partial hearing" } } }

    it "has answer options" do
      expected_answer_options = {
        "illness_type_1" => { "value" => "Vision - such as blindness or partial sight" },
        "illness_type_2" => { "value" => "Hearing - such as deafness or partial hearing" },
      }
      expect(question.answer_options).to eq(expected_answer_options)
    end

    it "can map yes values" do
      expect(question).to be_value_is_yes(1)
      expect(question).not_to be_value_is_yes(0)
    end
  end

  context "when the question is read only" do
    let(:readonly) { true }

    it "has a read only helper" do
      expect(question.read_only?).to be true
    end

    context "when the answer is part of a sum" do
      let(:question_id) { "pscharge" }

      it "has a result_field" do
        expect(question.result_field).to eq("tcharge")
      end

      it "has fields to sum" do
        expected_fields_to_sum = %w[brent scharge pscharge supcharg]
        expect(question.fields_to_add).to eq(expected_fields_to_sum)
      end
    end
  end

  context "with a lettings log" do
    let(:lettings_log) { FactoryBot.build(:lettings_log, :in_progress) }
    let(:question_id) { "incfreq" }
    let(:type) { "radio" }
    let(:answer_options) { { "1" => { "value" => "Weekly" }, "2" => { "value" => "Monthly" }, "3" => { "value" => "Yearly", "depends_on" => true } } }

    it "has an answer label" do
      lettings_log.incfreq = 1
      expect(question.answer_label(lettings_log)).to eq("Weekly")
    end

    it "has an update answer link text helper" do
      expect(question.action_text(lettings_log)).to match(/Answer/)
      lettings_log["incfreq"] = 0
      expect(question.action_text(lettings_log)).to match(/Change/)
    end

    context "when the question has an inferred answer" do
      let(:lettings_log) { FactoryBot.build(:lettings_log, :in_progress, postcode_known: 0, postcode_full: nil) }
      let(:question_id) { "incfreq" }
      let(:type) { "radio" }

      it "displays 'change' in the check answers link text" do
        expect(question.action_text(lettings_log)).to match(/Change/)
      end
    end

    context "when the answer option is a derived answer option" do
      let(:lettings_log) { FactoryBot.build(:lettings_log, :in_progress, incfreq: 3, postcode_full: nil) }
      let(:depends_on_met) { true }

      it "knows it has an inferred value or is derived for check answers" do
        expect(question.is_derived_or_has_inferred_check_answers_value?(lettings_log)).to be true
      end
    end

    context "when type is date" do
      let(:question_id) { "mrcdate" }
      let(:type) { "date" }

      it "displays a formatted answer label" do
        lettings_log.mrcdate = Time.zone.local(2021, 10, 11)
        expect(question.answer_label(lettings_log)).to eq("11 October 2021")
      end

      it "can handle nils" do
        lettings_log.mrcdate = nil
        expect(question.answer_label(lettings_log)).to eq("")
      end
    end

    context "when type is checkbox" do
      let(:question_id) { "housingneeds_type" }
      let(:type) { "checkbox" }
      let(:answer_options) do
        {
          "housingneeds_a" => { "value" => "Fully wheelchair accessible housing" },
          "housingneeds_b" => { "value" => "Wheelchair access to essential rooms" },
          "housingneeds_c" => { "value" => "Level access housing" },
        }
      end

      it "has a joined answers label" do
        lettings_log.housingneeds_a = 1
        lettings_log.housingneeds_c = 1
        expected_answer_label = "Fully wheelchair accessible housing, Level access housing"
        expect(question.answer_label(lettings_log)).to eq(expected_answer_label)
      end
    end

    context "when a condition is present" do
      let(:question_id) { "conditional_question" }
      let(:conditional_question_conditions) { [{ to: question_id, from: "hb", cond: [0] }] }
      let(:form_questions) { [OpenStruct.new(id: "hb", type: "radio")] }

      it "knows whether it is enabled or not for unmet conditions" do
        expect(question.enabled?(lettings_log)).to be false
      end

      it "knows whether it is enabled or not for met conditions" do
        lettings_log.hb = "Housing benefit"
        expect(question.enabled?(lettings_log)).to be true
      end

      context "when the condition type hasn't been implemented yet" do
        let(:form_questions) { [OpenStruct.new(id: "hb", type: "unkown")] }

        it "raises an exception" do
          expect { question.enabled?(lettings_log) }.to raise_error("Not implemented yet")
        end
      end
    end

    context "when answers have a suffix dependent on another answer" do
      let(:question_id) { "earnings" }
      let(:type) { "numeric" }
      let(:suffix) do
        [
          { "label" => " every week", "depends_on" => { "incfreq" => 1 } },
          { "label" => " every month", "depends_on" => { "incfreq" => 2 } },
          { "label" => " every year", "depends_on" => { "incfreq" => 3 } },
        ]
      end
      let(:prefix) { "£" }

      it "displays the correct label for given suffix and answer the suffix depends on" do
        lettings_log.incfreq = 1
        lettings_log.earnings = 500
        expect(question.answer_label(lettings_log)).to eq("£500.00 every week")
        lettings_log.incfreq = 2
        expect(question.answer_label(lettings_log)).to eq("£500.00 every month")
        lettings_log.incfreq = 3
        expect(question.answer_label(lettings_log)).to eq("£500.00 every year")
      end
    end

    context "with inferred_check_answers_value" do
      context "when Lettings form" do
        let(:question_id) { "armedforces" }
        let(:inferred_check_answers_value) { [{ "condition" => { "armedforces" => 3 }, "value" => "Prefers not to say" }] }

        it "returns the inferred label value" do
          lettings_log.armedforces = 3
          expect(question.answer_label(lettings_log)).to eq("Prefers not to say")
        end
      end

      context "when Sales form" do
        let(:question_id) { "national" }
        let(:sales_log) { FactoryBot.build(:sales_log, :completed, national: 13) }
        let(:inferred_check_answers_value) { [{ "condition" => { "national" => 13 }, "value" => "Prefers not to say" }] }

        it "returns the inferred label value" do
          expect(question.answer_label(sales_log)).to eq("Prefers not to say")
        end
      end
    end
  end

  describe ".completed?" do
    context "when the question has inferred value only for check answers display" do
      it "returns true" do
        lettings_log["postcode_known"] = 0
        expect(question.completed?(lettings_log)).to be(true)
      end
    end
  end

  context "when the question has a hidden in check answers attribute with dependencies" do
    let(:lettings_log) { FactoryBot.build(:lettings_log, :in_progress) }

    context "when it's hidden in check answers" do
      let(:depends_on_met) { true }

      it "can work out if the question will be shown in check answers" do
        expect(question.hidden_in_check_answers?(lettings_log, nil)).to be(true)
      end
    end

    context "when it's not hidden in check answers" do
      let(:depends_on_met) { false }

      it "can work out if the question will be shown in check answers" do
        expect(question.hidden_in_check_answers?(lettings_log, nil)).to be(false)
      end
    end
  end
end