defmodule Microwaveprop.PartitionManagerTest do
  use Microwaveprop.DataCase, async: true

  alias Microwaveprop.PartitionManager
  alias Microwaveprop.Repo

  # A throwaway parent table created per-test inside the sandbox
  # transaction. Postgres rolls back the DDL when the test ends, so
  # there's no cleanup. Naming is unique-ish to avoid collision if
  # async workers ever cross paths.
  defp synthetic_parent do
    "test_partitioned_#{System.unique_integer([:positive])}"
  end

  defp create_partitioned_parent!(name) do
    Repo.query!("""
    CREATE TABLE public."#{name}" (
      valid_time timestamp without time zone NOT NULL,
      payload integer
    ) PARTITION BY RANGE (valid_time)
    """)
  end

  defp partitions_of(parent) do
    %Postgrex.Result{rows: rows} =
      Repo.query!(
        """
        SELECT child.relname, pg_get_expr(child.relpartbound, child.oid)
        FROM pg_inherits
        JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
        JOIN pg_class child  ON pg_inherits.inhrelid  = child.oid
        WHERE parent.relname = $1
        ORDER BY child.relname
        """,
        [parent]
      )

    Enum.map(rows, fn [name, bound] -> {name, bound} end)
  end

  describe "ensure_quarterly_partitions/2" do
    test "creates current-quarter partition + lookahead quarters" do
      parent = synthetic_parent()
      create_partitioned_parent!(parent)

      results = PartitionManager.ensure_quarterly_partitions(2, [parent])

      # 1 current quarter + 2 lookahead = 3 partitions total.
      assert length(results) == 3
      assert Enum.all?(results, fn {p, _, status} -> p == parent and status == :created end)

      partitions = partitions_of(parent)
      assert length(partitions) == 3

      # Each bound must be a 3-month range starting on a quarter
      # boundary (Jan/Apr/Jul/Oct), with the next bound starting
      # exactly where the previous one ended.
      bounds =
        Enum.map(partitions, fn {_name, bound} ->
          [_, lo, hi] = Regex.run(~r/FROM \('([^']+)'\) TO \('([^']+)'\)/, bound)
          {NaiveDateTime.from_iso8601!(lo), NaiveDateTime.from_iso8601!(hi)}
        end)

      # Pass `NaiveDateTime` as the comparator module — default
      # `<=` falls back to term comparison, which is not chronological.
      sorted = Enum.sort_by(bounds, fn {lo, _} -> lo end, NaiveDateTime)

      Enum.each(sorted, fn {lo, hi} ->
        # Quarter starts on the 1st of Jan/Apr/Jul/Oct at midnight.
        assert lo.day == 1
        assert lo.hour == 0
        assert lo.minute == 0
        assert lo.month in [1, 4, 7, 10]
        # Range is exactly 3 months.
        assert NaiveDateTime.diff(hi, lo, :day) in 89..92
      end)

      # Adjacent quarters touch — no gaps, no overlaps.
      [{_, q1_hi}, {q2_lo, _} | _] = sorted
      assert q1_hi == q2_lo
    end

    test "is idempotent — re-running returns :exists for everything" do
      parent = synthetic_parent()
      create_partitioned_parent!(parent)

      _ = PartitionManager.ensure_quarterly_partitions(1, [parent])
      results = PartitionManager.ensure_quarterly_partitions(1, [parent])

      assert Enum.all?(results, fn {_, _, status} -> status == :exists end)
      # Still only 2 partitions on the parent (current + 1 lookahead).
      assert length(partitions_of(parent)) == 2
    end

    test "names partitions <parent>_YYYY_MM with the quarter-start month" do
      parent = synthetic_parent()
      create_partitioned_parent!(parent)

      _ = PartitionManager.ensure_quarterly_partitions(4, [parent])

      names = parent |> partitions_of() |> Enum.map(&elem(&1, 0))

      Enum.each(names, fn name ->
        # parent + "_" + YYYY + "_" + MM where MM is one of the
        # quarter-start months. Anything else means the math is off.
        assert Regex.match?(~r/^#{parent}_\d{4}_(01|04|07|10)$/, name)
      end)
    end

    test "spans year rollover correctly" do
      parent = synthetic_parent()
      create_partitioned_parent!(parent)

      # Lookahead big enough that we're guaranteed to cross at least
      # one calendar year regardless of when the test runs (worst
      # case is current quarter == Q1, where 4 lookahead = Q1 → Q1
      # next year = same month, so 5 lookahead is the minimum).
      _ = PartitionManager.ensure_quarterly_partitions(5, [parent])

      years =
        parent
        |> partitions_of()
        |> Enum.map(fn {name, _} ->
          [_, year_str, _] = Regex.run(~r/_(\d{4})_(\d{2})$/, name)
          String.to_integer(year_str)
        end)
        |> Enum.uniq()

      assert length(years) >= 2
    end
  end

  describe "ensure_partition/3" do
    test "returns :created on first call, :exists on second" do
      parent = synthetic_parent()
      create_partitioned_parent!(parent)

      q_start = ~D[2099-01-01]
      q_end = ~D[2099-04-01]

      assert {^parent, name, :created} = PartitionManager.ensure_partition(parent, q_start, q_end)
      assert {^parent, ^name, :exists} = PartitionManager.ensure_partition(parent, q_start, q_end)
      assert name == "#{parent}_2099_01"
    end
  end
end
