defmodule Towerops.Preseem.DeviceBaselineTest do
  use Towerops.DataCase, async: true

  import Towerops.AccountsFixtures
  import Towerops.OrganizationsFixtures

  alias Towerops.Preseem.AccessPoint
  alias Towerops.Preseem.DeviceBaseline

  setup do
    user = user_fixture()
    org = organization_fixture(user.id)

    {:ok, ap} =
      %AccessPoint{}
      |> AccessPoint.changeset(%{organization_id: org.id, preseem_id: "ap-baseline-1", name: "Baseline AP"})
      |> Repo.insert()

    %{access_point: ap}
  end

  describe "changeset/2" do
    test "valid changeset with all fields", %{access_point: ap} do
      attrs = %{
        preseem_access_point_id: ap.id,
        metric_name: "qoe_score",
        period: "busy",
        mean: 85.5,
        stddev: 4.2,
        p5: 72.0,
        p95: 95.3,
        sample_count: 336,
        computed_at: DateTime.truncate(DateTime.utc_now(), :second)
      }

      changeset = DeviceBaseline.changeset(%DeviceBaseline{}, attrs)
      assert changeset.valid?
    end

    test "requires preseem_access_point_id" do
      attrs = %{
        metric_name: "qoe_score",
        period: "busy",
        mean: 85.5,
        sample_count: 100,
        computed_at: DateTime.truncate(DateTime.utc_now(), :second)
      }

      changeset = DeviceBaseline.changeset(%DeviceBaseline{}, attrs)
      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).preseem_access_point_id
    end

    test "requires metric_name" do
      attrs = %{
        preseem_access_point_id: Ecto.UUID.generate(),
        period: "busy",
        mean: 85.5,
        sample_count: 100,
        computed_at: DateTime.truncate(DateTime.utc_now(), :second)
      }

      changeset = DeviceBaseline.changeset(%DeviceBaseline{}, attrs)
      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).metric_name
    end

    test "requires period" do
      attrs = %{
        preseem_access_point_id: Ecto.UUID.generate(),
        metric_name: "qoe_score",
        mean: 85.5,
        sample_count: 100,
        computed_at: DateTime.truncate(DateTime.utc_now(), :second)
      }

      changeset = DeviceBaseline.changeset(%DeviceBaseline{}, attrs)
      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).period
    end

    test "requires mean" do
      attrs = %{
        preseem_access_point_id: Ecto.UUID.generate(),
        metric_name: "qoe_score",
        period: "busy",
        sample_count: 100,
        computed_at: DateTime.truncate(DateTime.utc_now(), :second)
      }

      changeset = DeviceBaseline.changeset(%DeviceBaseline{}, attrs)
      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).mean
    end

    test "requires sample_count" do
      attrs = %{
        preseem_access_point_id: Ecto.UUID.generate(),
        metric_name: "qoe_score",
        period: "busy",
        mean: 85.5,
        computed_at: DateTime.truncate(DateTime.utc_now(), :second)
      }

      changeset = DeviceBaseline.changeset(%DeviceBaseline{}, attrs)
      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).sample_count
    end

    test "requires computed_at" do
      attrs = %{
        preseem_access_point_id: Ecto.UUID.generate(),
        metric_name: "qoe_score",
        period: "busy",
        mean: 85.5,
        sample_count: 100
      }

      changeset = DeviceBaseline.changeset(%DeviceBaseline{}, attrs)
      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).computed_at
    end

    test "validates period inclusion" do
      attrs = %{
        preseem_access_point_id: Ecto.UUID.generate(),
        metric_name: "qoe_score",
        period: "invalid_period",
        mean: 85.5,
        sample_count: 100,
        computed_at: DateTime.truncate(DateTime.utc_now(), :second)
      }

      changeset = DeviceBaseline.changeset(%DeviceBaseline{}, attrs)
      refute changeset.valid?
      assert "is invalid" in errors_on(changeset).period
    end

    test "accepts all valid periods" do
      for period <- ~w(busy offpeak all) do
        attrs = %{
          preseem_access_point_id: Ecto.UUID.generate(),
          metric_name: "qoe_score",
          period: period,
          mean: 85.5,
          sample_count: 100,
          computed_at: DateTime.truncate(DateTime.utc_now(), :second)
        }

        changeset = DeviceBaseline.changeset(%DeviceBaseline{}, attrs)
        assert changeset.valid?, "Expected period '#{period}' to be valid"
      end
    end

    test "validates metric_name inclusion" do
      attrs = %{
        preseem_access_point_id: Ecto.UUID.generate(),
        metric_name: "invalid_metric",
        period: "busy",
        mean: 85.5,
        sample_count: 100,
        computed_at: DateTime.truncate(DateTime.utc_now(), :second)
      }

      changeset = DeviceBaseline.changeset(%DeviceBaseline{}, attrs)
      refute changeset.valid?
      assert "is invalid" in errors_on(changeset).metric_name
    end

    test "accepts all valid metric names" do
      valid_metrics =
        ~w(qoe_score capacity_score rf_score p95_latency avg_latency avg_jitter avg_loss avg_throughput subscriber_count airtime_utilization)

      for metric <- valid_metrics do
        attrs = %{
          preseem_access_point_id: Ecto.UUID.generate(),
          metric_name: metric,
          period: "busy",
          mean: 85.5,
          sample_count: 100,
          computed_at: DateTime.truncate(DateTime.utc_now(), :second)
        }

        changeset = DeviceBaseline.changeset(%DeviceBaseline{}, attrs)
        assert changeset.valid?, "Expected metric '#{metric}' to be valid"
      end
    end

    test "persists and reloads from database", %{access_point: ap} do
      now = DateTime.truncate(DateTime.utc_now(), :second)

      attrs = %{
        preseem_access_point_id: ap.id,
        metric_name: "qoe_score",
        period: "busy",
        mean: 85.5,
        stddev: 4.2,
        p5: 72.0,
        p95: 95.3,
        sample_count: 336,
        computed_at: now
      }

      {:ok, baseline} = %DeviceBaseline{} |> DeviceBaseline.changeset(attrs) |> Repo.insert()
      reloaded = Repo.get!(DeviceBaseline, baseline.id)

      assert reloaded.preseem_access_point_id == ap.id
      assert reloaded.metric_name == "qoe_score"
      assert reloaded.period == "busy"
      assert reloaded.mean == 85.5
      assert reloaded.stddev == 4.2
      assert reloaded.p5 == 72.0
      assert reloaded.p95 == 95.3
      assert reloaded.sample_count == 336
      assert reloaded.computed_at == now
    end

    test "enforces unique constraint on [access_point_id, metric_name, period]", %{access_point: ap} do
      now = DateTime.truncate(DateTime.utc_now(), :second)

      attrs = %{
        preseem_access_point_id: ap.id,
        metric_name: "qoe_score",
        period: "busy",
        mean: 85.5,
        sample_count: 336,
        computed_at: now
      }

      {:ok, _} = %DeviceBaseline{} |> DeviceBaseline.changeset(attrs) |> Repo.insert()

      assert {:error, changeset} =
               %DeviceBaseline{} |> DeviceBaseline.changeset(attrs) |> Repo.insert()

      assert errors_on(changeset).preseem_access_point_id != []
    end
  end
end
