require "test_helper"

class Holding::MaterializerTest < ActiveSupport::TestCase
  include EntriesTestHelper

  setup do
    @family = families(:empty)
    @account = @family.accounts.create!(name: "Test", balance: 20000, cash_balance: 20000, currency: "USD", accountable: Investment.new)
    @aapl = securities(:aapl)
  end

  test "syncs holdings" do
    create_trade(@aapl, account: @account, qty: 1, price: 200, date: Date.current)

    # Should have yesterday's and today's holdings
    assert_difference "@account.holdings.count", 2 do
      Holding::Materializer.new(@account, strategy: :forward).materialize_holdings
    end
  end

  test "purges stale holdings for unlinked accounts" do
    # Since the account has no entries, there should be no holdings
    Holding.create!(account: @account, security: @aapl, qty: 1, price: 100, amount: 100, currency: "USD", date: Date.current)

    assert_difference "Holding.count", -1 do
      Holding::Materializer.new(@account, strategy: :forward).materialize_holdings
    end
  end

  test "preserves provider cost_basis when trade-derived cost_basis is nil" do
    # Simulate a provider-imported holding with cost_basis (e.g., from SimpleFIN)
    # This is the realistic scenario: linked account with provider holdings but no trades
    provider_cost_basis = BigDecimal("150.00")
    holding = Holding.create!(
      account: @account,
      security: @aapl,
      qty: 10,
      price: 200,
      amount: 2000,
      currency: "USD",
      date: Date.current,
      cost_basis: provider_cost_basis
    )

    # Use :reverse strategy (what linked accounts use) - doesn't purge holdings
    # The AAPL holding has no trades, so computed cost_basis is nil
    # The materializer should preserve the provider cost_basis, not overwrite with nil
    Holding::Materializer.new(@account, strategy: :reverse).materialize_holdings

    holding.reload
    assert_equal provider_cost_basis, holding.cost_basis,
      "Provider cost_basis should be preserved when no trades exist for this security"
  end

  test "updates cost_basis when trade-derived cost_basis is available" do
    # Create a holding with provider cost_basis
    Holding.create!(
      account: @account,
      security: @aapl,
      qty: 10,
      price: 200,
      amount: 2000,
      currency: "USD",
      date: Date.current,
      cost_basis: BigDecimal("150.00")  # Provider says $150
    )

    # Create a trade that gives us a different cost basis
    create_trade(@aapl, account: @account, qty: 10, price: 180, date: Date.current)

    # Use :reverse strategy - with trades, it should compute cost_basis from them
    Holding::Materializer.new(@account, strategy: :reverse).materialize_holdings

    holding = @account.holdings.find_by(security: @aapl, date: Date.current)
    assert_equal BigDecimal("180.00"), holding.cost_basis,
      "Trade-derived cost_basis should override provider cost_basis when available"
  end

  test "recalculates calculated cost_basis when new trades are added" do
    date = Date.current

    create_trade(@aapl, account: @account, qty: 1, price: 3000, date: date)
    Holding::Materializer.new(@account, strategy: :forward).materialize_holdings

    holding = @account.holdings.find_by!(security: @aapl, date: date, currency: "USD")
    assert_equal "calculated", holding.cost_basis_source
    assert_equal BigDecimal("3000.0"), holding.cost_basis

    create_trade(@aapl, account: @account, qty: 1, price: 2500, date: date)
    Holding::Materializer.new(@account, strategy: :forward).materialize_holdings

    holding.reload
    assert_equal "calculated", holding.cost_basis_source
    assert_equal BigDecimal("2750.0"), holding.cost_basis
  end

  test "preserves calculated history for provider-sourced holdings on reverse materialization" do
    coinstats_item = @family.coinstats_items.create!(name: "CoinStats", api_key: "test-key")
    coinstats_account = coinstats_item.coinstats_accounts.create!(
      name: "Brokerage",
      currency: "USD"
    )
    account_provider = AccountProvider.create!(account: @account, provider: coinstats_account)

    Holding.create!(
      account: @account,
      security: @aapl,
      qty: 10,
      price: 200,
      amount: 2000,
      currency: "USD",
      date: Date.current,
      account_provider: account_provider
    )

    Holding::Materializer.new(@account, strategy: :reverse).materialize_holdings

    today_holding = @account.holdings.find_by!(security: @aapl, date: Date.current, currency: "USD")
    yesterday_holding = @account.holdings.find_by!(security: @aapl, date: Date.yesterday, currency: "USD")

    assert_equal account_provider.id, today_holding.account_provider_id
    assert_nil yesterday_holding.account_provider_id
    assert_equal BigDecimal("10"), yesterday_holding.qty
    assert_equal yesterday_holding.qty * yesterday_holding.price, yesterday_holding.amount
  end
end
