defmodule MicrowavepropWeb.Plugs.RemoteIpPropertyTest do
  @moduledoc """
  Property tests for the CIDR-matching logic in
  `MicrowavepropWeb.Plugs.RemoteIp`. The `peer_trusted?` helper is
  private, so we exercise it via the plug's `init/1` + `call/2`
  pipeline: set up a trusted-proxy list, hand the plug a conn whose
  `remote_ip` we control, and assert whether the forwarded-IP header
  was honoured.
  """

  use ExUnit.Case, async: true
  use ExUnitProperties

  import ExUnit.CaptureLog

  alias MicrowavepropWeb.Plugs.RemoteIp

  # The X-Forwarded-For value we supply whenever we want to check
  # whether the peer was trusted.
  @forwarded_ipv4 "198.51.100.7"
  @forwarded_tuple {198, 51, 100, 7}

  # ── Generators ──────────────────────────────────────────────────

  defp ipv4_octet, do: integer(0..255)

  defp ipv4_tuple_gen do
    gen all(
          a <- ipv4_octet(),
          b <- ipv4_octet(),
          c <- ipv4_octet(),
          d <- ipv4_octet()
        ) do
      {a, b, c, d}
    end
  end

  defp ipv4_string(tuple) do
    tuple |> Tuple.to_list() |> Enum.join(".")
  end

  # ── Helpers ─────────────────────────────────────────────────────

  defp call_plug(remote_ip, trusted_cidrs) do
    opts = RemoteIp.init(trusted_proxies: trusted_cidrs)

    conn =
      Phoenix.ConnTest.build_conn()
      |> Map.put(:remote_ip, remote_ip)
      |> Plug.Conn.put_req_header("x-forwarded-for", @forwarded_ipv4)

    RemoteIp.call(conn, opts)
  end

  defp trusted?(remote_ip, cidrs) do
    call_plug(remote_ip, cidrs).remote_ip == @forwarded_tuple
  end

  defp in_cidr?({a, b, c, d}, {{na, nb, nc, nd}, prefix}) do
    host = <<a, b, c, d>>
    network = <<na, nb, nc, nd>>
    <<host_prefix::bitstring-size(prefix), _::bitstring>> = host
    <<net_prefix::bitstring-size(prefix), _::bitstring>> = network
    host_prefix == net_prefix
  end

  # ── Properties ──────────────────────────────────────────────────

  property "/0 trusts every IPv4 peer" do
    check all(peer <- ipv4_tuple_gen()) do
      assert trusted?(peer, ["0.0.0.0/0"])
    end
  end

  property "/32 trusts only the exact IP" do
    check all(
            anchor <- ipv4_tuple_gen(),
            other <- ipv4_tuple_gen(),
            anchor != other
          ) do
      cidrs = [ipv4_string(anchor) <> "/32"]
      assert trusted?(anchor, cidrs)
      refute trusted?(other, cidrs)
    end
  end

  property "every X.Y.Z.W in 10.0.0.0/8 is trusted; nothing outside is" do
    cidrs = ["10.0.0.0/8"]

    check all(ip <- ipv4_tuple_gen()) do
      inside = in_cidr?(ip, {{10, 0, 0, 0}, 8})
      assert trusted?(ip, cidrs) == inside
    end
  end

  property "arbitrary /prefix CIDRs match iff the peer's high-order prefix bits agree" do
    check all(
            network <- ipv4_tuple_gen(),
            prefix <- integer(1..32),
            peer <- ipv4_tuple_gen()
          ) do
      cidr = ipv4_string(network) <> "/" <> Integer.to_string(prefix)
      assert trusted?(peer, [cidr]) == in_cidr?(peer, {network, prefix})
    end
  end

  property "union of multiple CIDRs matches the logical OR of each" do
    check all(
            a <- ipv4_tuple_gen(),
            prefix_a <- integer(4..32),
            b <- ipv4_tuple_gen(),
            prefix_b <- integer(4..32),
            peer <- ipv4_tuple_gen()
          ) do
      cidrs = [
        ipv4_string(a) <> "/" <> Integer.to_string(prefix_a),
        ipv4_string(b) <> "/" <> Integer.to_string(prefix_b)
      ]

      expected = in_cidr?(peer, {a, prefix_a}) or in_cidr?(peer, {b, prefix_b})
      assert trusted?(peer, cidrs) == expected
    end
  end

  property "/0 as the only CIDR still trusts everything even with bogus entries mixed in" do
    # Invalid CIDR strings are dropped at init time with a warning;
    # the surviving /0 should still match every peer.
    capture_log(fn ->
      check all(peer <- ipv4_tuple_gen()) do
        assert trusted?(peer, ["0.0.0.0/0", "not-a-cidr", "999.999.999.999/24", "10.0.0.0/33"])
      end
    end)
  end

  property "an empty trusted-proxy list rejects every peer" do
    check all(peer <- ipv4_tuple_gen()) do
      # No CIDRs → the forwarded header must not flow through.
      refute trusted?(peer, [])
    end
  end
end
