# frozen_string_literal: true

RSpec.describe DividendComputationGeneration do
  let(:company) { create(:company) }

  def seed_data
    @seed_class = create(:share_class, company:, name: "Seed", original_issue_price_in_dollars: 1.1234, hurdle_rate: 12, preferred: true)
    @A_class = create(:share_class, company:, name: "Series A", original_issue_price_in_dollars: 1.2389, hurdle_rate: 7, preferred: true)
    @common_class = create(:share_class, company:, name: "Common", original_issue_price_in_dollars: nil, hurdle_rate: nil)

    # Deactivated investor with no shares or convertibles - should not appear in results
    create(:company_investor, company:, deactivated_at: 1.day.ago, total_shares: 0, investment_amount_in_cents: 0, total_options: 0)

    @seed_investor = create(:company_investor, user: create(:user, legal_name: "Seed Investor"), company:)
    create(:share_holding, company_investor: @seed_investor, share_class: @seed_class, number_of_shares: 99_283, originally_acquired_at: 91.days.ago, total_amount_in_cents: 111_406_00)
    create(:share_holding, company_investor: @seed_investor, share_class: @seed_class, number_of_shares: 12_123, originally_acquired_at: 89.days.ago, total_amount_in_cents: 13_625_00)

    @series_A_investor = create(:company_investor, user: create(:user, legal_name: "Series A Investor"), company:)
    create(:share_holding, company_investor: @series_A_investor, share_class: @A_class, number_of_shares: 32_123, total_amount_in_cents: 39_768_00)
    create(:share_holding, company_investor: @series_A_investor, share_class: @A_class, number_of_shares: 1_346, total_amount_in_cents: 1_666_00)

    @seed_and_series_A_investor = create(:company_investor,
                                         user: create(:user, legal_name: "Seed & Series A Investor"),
                                         company:)
    create(:share_holding, company_investor: @seed_and_series_A_investor, share_class: @seed_class, number_of_shares: 3_098, total_amount_in_cents: 3_480_00)
    create(:share_holding, company_investor: @seed_and_series_A_investor, share_class: @seed_class, number_of_shares: 4_820, total_amount_in_cents: 5_417_00)
    create(:share_holding, company_investor: @seed_and_series_A_investor, share_class: @A_class,
                           number_of_shares: 2_934, total_amount_in_cents: 3_632_00)
    create(:share_holding, company_investor: @seed_and_series_A_investor, share_class: @A_class,
                           number_of_shares: 1_589, total_amount_in_cents: 1_967_00)

    @common_investor = create(:company_investor, user: create(:user, legal_name: "Common Investor"), company:)
    create(:share_holding, company_investor: @common_investor, share_class: @common_class, number_of_shares: 123, originally_acquired_at: 30.days.ago, total_amount_in_cents: 138_00)
    create(:share_holding, company_investor: @common_investor, share_class: @common_class, number_of_shares: 768, originally_acquired_at: 31.days.ago, total_amount_in_cents: 863_00)

    @all_class_investor = create(:company_investor, user: create(:user, legal_name: "All class Investor"), company:)
    create(:share_holding, company_investor: @all_class_investor, share_class: @seed_class, number_of_shares: 9_876, total_amount_in_cents: 11_100_00)
    create(:share_holding, company_investor: @all_class_investor, share_class: @seed_class, number_of_shares: 5_432, total_amount_in_cents: 6_105_00)
    create(:share_holding, company_investor: @all_class_investor, share_class: @A_class, number_of_shares: 1_987, total_amount_in_cents: 2_460_00)
    create(:share_holding, company_investor: @all_class_investor, share_class: @A_class, number_of_shares: 6_543, total_amount_in_cents: 8_103_00)
    create(:share_holding, company_investor: @all_class_investor, share_class: @common_class, number_of_shares: 210, total_amount_in_cents: 236_00)
    create(:share_holding, company_investor: @all_class_investor, share_class: @common_class, number_of_shares: 987, total_amount_in_cents: 1_109_00)

    @entire_safe_owner = create(:company_investor, company:, user: create(:user, legal_name: "Richie Rich LLC"))
    @safe1 = create(:convertible_investment, company:, entity_name: "Richie Rich LLC", implied_shares: 987_632,
                                             amount_in_cents: 1_000_000_00)
    create(:convertible_security, company_investor: @entire_safe_owner, convertible_investment: @safe1,
                                  implied_shares: 987_632, principal_value_in_cents: 1_000_000_00)

    @safe2 = create(:convertible_investment, company:, entity_name: "Wefunder", implied_shares: 497_092,
                                             amount_in_cents: 2_000_000_00)
    @partial_safe_owner1 = create(:company_investor, company:,)
    @partial_safe_owner2 = create(:company_investor, company:,)
    @partial_safe_owner3 = create(:company_investor, company:,)
    create(:convertible_security, company_investor: @partial_safe_owner1, convertible_investment: @safe2,
                                  implied_shares: 497_092.to_d / 2_000_000_00.to_d * 123_456_78.to_d,
                                  principal_value_in_cents: 123_456_78)
    create(:convertible_security, company_investor: @partial_safe_owner2, convertible_investment: @safe2,
                                  implied_shares: 497_092.to_d / 2_000_000_00.to_d * 910_234_56.to_d,
                                  principal_value_in_cents: 910_234_56)
    create(:convertible_security, company_investor: @partial_safe_owner3, convertible_investment: @safe2,
                                  implied_shares: 497_092.to_d / 2_000_000_00.to_d * 966_308_66.to_d,
                                  principal_value_in_cents: 966_308_66)
  end

  describe "validation" do
    it "raises error when dividend amount is insufficient for preferred dividends" do
      seed_data

      # Preferred dividends require $22,184.03
      insufficient_amount = 20_000 # Less than required preferred dividend amount

      expect do
        described_class.new(company, amount_in_usd: insufficient_amount, return_of_capital: false).process
      end.to raise_error(
        DividendComputationGeneration::InsufficientFundsError,
        "Sorry, you cannot distribute $20000.0 as preferred investors require a payout of at least $22184.03."
      )
    end

    it "raises error when company has no eligible investors" do
      # Don't call seed_data - this creates an empty company with no share holdings or convertible investments

      expect do
        described_class.new(company, amount_in_usd: 10_000, return_of_capital: false).process
      end.to raise_error(
        DividendComputationGeneration::NoEligibleInvestorsError,
        "Sorry, we couldn't find any eligible investors to receive dividends. Please make sure your company has investors with shares or convertible securities before creating a dividend distribution."
      )
    end

    it "generates only preferred dividends when amount equals preferred dividend requirement" do
      seed_data

      amount_equal_to_preferred = 22_184.03

      dividend_computation = described_class.new(company, amount_in_usd: amount_equal_to_preferred, return_of_capital: false).process
      expect(dividend_computation).to be_present

      # Should have preferred dividend outputs
      preferred_outputs = dividend_computation.dividend_computation_outputs.where.not(preferred_dividend_amount_in_usd: 0)
      expect(preferred_outputs.count).to be > 0

      # Should NOT have common dividend outputs (since available_amount = 0)
      common_outputs = dividend_computation.dividend_computation_outputs.where.not(dividend_amount_in_usd: 0)
      expect(common_outputs.count).to eq(0)

      # Total should equal the input amount
      expect(dividend_computation.dividend_computation_outputs.sum(:total_amount_in_usd)).to eq(amount_equal_to_preferred)
    end

    it "generates both preferred and common dividends when amount exceeds preferred dividend requirement" do
      seed_data
      amount_exceeding_preferred = 25_000

      dividend_computation = described_class.new(company, amount_in_usd: amount_exceeding_preferred, return_of_capital: false).process
      expect(dividend_computation).to be_present

      # Should have preferred dividend outputs
      preferred_outputs = dividend_computation.dividend_computation_outputs.where.not(preferred_dividend_amount_in_usd: 0)
      expect(preferred_outputs.count).to be > 0

      # Should also have common dividend outputs (since available_amount > 0)
      common_outputs = dividend_computation.dividend_computation_outputs.where.not(dividend_amount_in_usd: 0)
      expect(common_outputs.count).to be > 0

      # Total should equal the input amount
      total_amount = dividend_computation.dividend_computation_outputs.sum(:total_amount_in_usd)
      expect(total_amount).to be_within(1.0).of(amount_exceeding_preferred)
    end
  end

  it "generates records as expected" do
    seed_data

    dividend_computation = nil
    expect do
      dividend_computation = described_class.new(company, amount_in_usd: 1_000_000, return_of_capital: false).process
    end.to change { company.dividend_computations.count }.by(1)
       .and change { DividendComputationOutput.count }.by(10) # 1 record per investor per share class

    expect(dividend_computation.company).to eq(company)
    expect(dividend_computation.total_amount_in_usd).to eq(1_000_000)
    expect(dividend_computation.return_of_capital).to eq(false)
    expect(dividend_computation.dividend_computation_outputs.count).to eq(10)

    # $ Available after paying dividend to preferred shares
    #   = Total - Sum of all preferred dividends
    #   = 1,000,000 - 22,184.03
    #   = $977,815.97
    # Total shares considered
    #   = company.convertible_investments.sum(:implied_shares) + company.share_holdings.sum(:number_of_shares)
    #   = 183,242 + (987,632 + 497,092)
    #   = 1,667,966

    # Seed Investor
    expect(dividend_computation.dividend_computation_outputs.exists?(
             company_investor_id: @seed_investor.id,
             share_class: "Seed",
             number_of_shares: 111_406, # 99_283 + 12_123,
             hurdle_rate: 12,
             original_issue_price_in_usd: 1.1234,
             preferred_dividend_amount_in_usd: 15_018.43, # ROUNDUP((12 / 100) * 1.1234 * 111406, 2)
             dividend_amount_in_usd: 65_309.83, # ROUNDUP(977815.97 * (111406 / 1667966), 2)
             qualified_dividend_amount_usd: 71_587.08, # ROUNDUP((12 / 100) * 1.1234 * 99283, 2) + ROUNDUP(977815.97 * (99283 / 1667966), 2)
             total_amount_in_usd: 80_328.26,
             investment_amount_cents: 125_031_00 # 11140600 + 1362500
           )).to eq(true)

    # Series A Investor
    expect(dividend_computation.dividend_computation_outputs.exists?(
             company_investor_id: @series_A_investor.id,
             share_class: "Series A",
             number_of_shares: 33_469, # 32_123 + 1_346
             hurdle_rate: 7,
             original_issue_price_in_usd: 1.2389,
             preferred_dividend_amount_in_usd: 2_902.54, # ROUNDUP((7 / 100) * 1.2389 * 33469, 2)
             dividend_amount_in_usd: 19_620.62, # ROUNDUP(977815.97 * (33469 / 1667966), 2)
             qualified_dividend_amount_usd: 22_523.16, # ROUNDUP((7 / 100) * 1.2389 * 33469, 2) + ROUNDUP(977815.97 * (33469 / 1667966), 2)
             total_amount_in_usd: 22_523.16,
             investment_amount_cents: 41_434_00 # 3976800 + 166600
           )).to eq(true)

    # Seed and Series A investor
    expect(dividend_computation.dividend_computation_outputs.exists?(
             company_investor_id: @seed_and_series_A_investor.id,
             share_class: "Seed",
             number_of_shares: 7_918, # 3_098 + 4_820
             hurdle_rate: 12,
             original_issue_price_in_usd: 1.1234,
             preferred_dividend_amount_in_usd: 1_067.41, # ROUNDUP((12 / 100) * 1.1234 * 7918, 2)
             dividend_amount_in_usd: 4_641.79, # ROUNDUP(977815.97 * (7918 / 1667966), 2)
             qualified_dividend_amount_usd: 5_709.20, # ROUNDUP((12 / 100) * 1.1234 * 7918, 2) + ROUNDUP(977815.97 * (7918 / 1667966), 2)
             total_amount_in_usd: 5_709.20,
             investment_amount_cents: 8_897_00 # 348000 + 541700
           )).to eq(true)
    expect(dividend_computation.dividend_computation_outputs.exists?(
             company_investor_id: @seed_and_series_A_investor.id,
             share_class: "Series A",
             number_of_shares: 4_523, # 2_934 + 1_589
             hurdle_rate: 7,
             original_issue_price_in_usd: 1.2389,
             preferred_dividend_amount_in_usd: 392.25, # ROUNDUP((7 / 100) * 1.2389 * 4523, 2)
             dividend_amount_in_usd: 2_651.53, # ROUNDUP(977815.97 * (4523 / 1667966), 2)
             qualified_dividend_amount_usd: 3_043.78, # ROUNDUP((7 / 100) * 1.2389 * 4523, 2) + ROUNDUP(977815.97 * (4523 / 1667966), 2)
             total_amount_in_usd: 3_043.78,
             investment_amount_cents: 5_599_00 # 363200 + 196700
           )).to eq(true)

    # Common Investor
    expect(dividend_computation.dividend_computation_outputs.exists?(
             company_investor_id: @common_investor.id,
             share_class: "Common",
             number_of_shares: 891, # 123 + 768
             hurdle_rate: nil,
             original_issue_price_in_usd: nil,
             preferred_dividend_amount_in_usd: 0,
             dividend_amount_in_usd: 522.34, # ROUNDUP(977815.97 * (891 / 1667966), 2)
             qualified_dividend_amount_usd: 0, # No eligible shares for qualified dividends
             total_amount_in_usd: 522.34,
             investment_amount_cents: 1_001_00 # 13800 + 86300
           )).to eq(true)

    # Investor with all share classes
    expect(dividend_computation.dividend_computation_outputs.exists?(
             company_investor_id: @all_class_investor.id,
             share_class: "Seed",
             number_of_shares: 15_308, # 9_876 + 5_432
             hurdle_rate: 12,
             original_issue_price_in_usd: 1.1234,
             preferred_dividend_amount_in_usd: 2_063.65, # ROUNDUP((12 / 100) * 1.1234 * 15308, 2)
             dividend_amount_in_usd: 8_974.05, # ROUNDUP(977815.97 * (15308 / 1667966), 2)
             qualified_dividend_amount_usd: 11_037.70, # ROUNDUP((12 / 100) * 1.1234 * 15308, 2) + ROUNDUP(977815.97 * (15308 / 1667966), 2)
             total_amount_in_usd: 11_037.70,
             investment_amount_cents: 17_205_00 # 11100000 + 6105000
           )).to eq(true)
    expect(dividend_computation.dividend_computation_outputs.exists?(
             company_investor_id: @all_class_investor.id,
             share_class: "Series A",
             number_of_shares: 8_530, # 1_987 + 6_543
             hurdle_rate: 7,
             original_issue_price_in_usd: 1.2389,
             preferred_dividend_amount_in_usd: 739.75, # ROUNDUP((7 / 100) * 1.2389 * 8530, 2)
             dividend_amount_in_usd: 5_000.57, # ROUNDUP(977815.97 * (8530 / 1667966), 2)
             qualified_dividend_amount_usd: 5_740.32, # ROUNDUP((7 / 100) * 1.2389 * 8530, 2) + ROUNDUP(977815.97 * (8530 / 1667966), 2)
             total_amount_in_usd: 5_740.32,
             investment_amount_cents: 10_563_00 # 2460000 + 8103000
           )).to eq(true)
    expect(dividend_computation.dividend_computation_outputs.exists?(
             company_investor_id: @all_class_investor.id,
             share_class: "Common",
             number_of_shares: 1_197, # 210 + 987
             hurdle_rate: nil,
             original_issue_price_in_usd: nil,
             preferred_dividend_amount_in_usd: 0,
             dividend_amount_in_usd: 701.73, # ROUNDUP(977815.97 * (1197 / 1667966), 2)
             qualified_dividend_amount_usd: 701.73, # ROUNDUP(977815.97 * (1197 / 1667966), 2)
             total_amount_in_usd: 701.73,
             investment_amount_cents: 1_345_00 # 23600 + 110900
           )).to eq(true)

    # SAFE 1 - "Richie Rich LLC"
    expect(dividend_computation.dividend_computation_outputs.exists?(
             investor_name: "Richie Rich LLC",
             share_class: @safe1.identifier,
             number_of_shares: 987_632,
             hurdle_rate: nil,
             original_issue_price_in_usd: nil,
             preferred_dividend_amount_in_usd: 0,
             dividend_amount_in_usd: 578_982.04, # ROUNDUP(977815.97 * (987632 / 1667966), 2)
             qualified_dividend_amount_usd: 578_982.04, # ROUNDUP(977815.97 * (987632 / 1667966), 2)
             total_amount_in_usd: 578_982.04,
             investment_amount_cents: 1_000_000_00 # Set to the convertible.amount_in_cents
           )).to eq(true)

    # SAFE 2 - "Wefunder"
    expect(dividend_computation.dividend_computation_outputs.exists?(
             investor_name: "Wefunder",
             share_class: @safe2.identifier,
             number_of_shares: 497_092,
             hurdle_rate: nil,
             original_issue_price_in_usd: nil,
             preferred_dividend_amount_in_usd: 0,
             dividend_amount_in_usd: 291_411.52, # ROUNDUP(977815.97 * (497092 / 1667966), 2)
             qualified_dividend_amount_usd: 291_411.52, # ROUNDUP(977815.97 * (497092 / 1667966), 2)
             total_amount_in_usd: 291_411.52,
             investment_amount_cents: 2_000_000_00 # Set to the convertible.amount_in_cents
           )).to eq(true)

    # Assert sum of all computed dividends
    # It's more than the input that is 1M because of rounding up. This was the case when Cooley did the calculation too.
    expect(dividend_computation.dividend_computation_outputs.sum(:total_amount_in_usd)).to eq(1_000_000.05)
  end
end
