defmodule Towerops.Accounts.UserConsentTest do
  use Towerops.DataCase, async: true

  import Towerops.AccountsFixtures

  alias Towerops.Accounts.UserConsent

  describe "consent_types/0" do
    test "returns list of valid consent types" do
      types = UserConsent.consent_types()
      assert "privacy_policy" in types
      assert "terms_of_service" in types
      assert length(types) == 2
    end
  end

  describe "current_version/1" do
    test "returns current version for privacy_policy" do
      assert UserConsent.current_version("privacy_policy") == "1.0"
    end

    test "returns current version for terms_of_service" do
      assert UserConsent.current_version("terms_of_service") == "1.0"
    end
  end

  describe "grant_changeset/2" do
    setup do
      user = user_fixture()
      {:ok, user: user}
    end

    test "valid attributes create valid changeset", %{user: user} do
      attrs = %{
        user_id: user.id,
        consent_type: "privacy_policy",
        version: "1.0",
        granted_at: DateTime.utc_now()
      }

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

    test "uses default struct when called with 1 argument", %{user: user} do
      attrs = %{
        user_id: user.id,
        consent_type: "terms_of_service",
        version: "1.0",
        granted_at: DateTime.utc_now()
      }

      changeset = UserConsent.grant_changeset(attrs)
      assert changeset.valid?
    end

    test "requires user_id" do
      attrs = %{
        consent_type: "privacy_policy",
        version: "1.0",
        granted_at: DateTime.utc_now()
      }

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

    test "requires consent_type", %{user: user} do
      attrs = %{
        user_id: user.id,
        version: "1.0",
        granted_at: DateTime.utc_now()
      }

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

    test "requires version", %{user: user} do
      attrs = %{
        user_id: user.id,
        consent_type: "privacy_policy",
        granted_at: DateTime.utc_now()
      }

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

    test "requires granted_at", %{user: user} do
      attrs = %{
        user_id: user.id,
        consent_type: "privacy_policy",
        version: "1.0"
      }

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

    test "validates consent_type is in valid list", %{user: user} do
      attrs = %{
        user_id: user.id,
        consent_type: "invalid_type",
        version: "1.0",
        granted_at: DateTime.utc_now()
      }

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

    test "validates foreign key constraint on user_id" do
      attrs = %{
        user_id: Ecto.UUID.generate(),
        consent_type: "privacy_policy",
        version: "1.0",
        granted_at: DateTime.utc_now()
      }

      changeset = UserConsent.grant_changeset(%UserConsent{}, attrs)
      assert changeset.valid?

      {:error, changeset} = Repo.insert(changeset)
      assert "does not exist" in errors_on(changeset).user_id
    end

    test "allows multiple consents for same user_id + consent_type + version", %{user: user} do
      attrs = %{
        user_id: user.id,
        consent_type: "privacy_policy",
        version: "1.0",
        granted_at: DateTime.utc_now()
      }

      # Insert first consent
      changeset1 = UserConsent.grant_changeset(%UserConsent{}, attrs)
      assert {:ok, _consent1} = Repo.insert(changeset1)

      # Insert another consent (allowed - e.g., user re-consents after revoking)
      changeset2 = UserConsent.grant_changeset(%UserConsent{}, attrs)
      assert {:ok, _consent2} = Repo.insert(changeset2)
    end

    test "allows same user to consent to different types", %{user: user} do
      privacy_attrs = %{
        user_id: user.id,
        consent_type: "privacy_policy",
        version: "1.0",
        granted_at: DateTime.utc_now()
      }

      terms_attrs = %{
        user_id: user.id,
        consent_type: "terms_of_service",
        version: "1.0",
        granted_at: DateTime.utc_now()
      }

      changeset1 = UserConsent.grant_changeset(%UserConsent{}, privacy_attrs)
      assert {:ok, _consent1} = Repo.insert(changeset1)

      changeset2 = UserConsent.grant_changeset(%UserConsent{}, terms_attrs)
      assert {:ok, _consent2} = Repo.insert(changeset2)
    end

    test "allows same user to consent to different versions", %{user: user} do
      v1_attrs = %{
        user_id: user.id,
        consent_type: "privacy_policy",
        version: "1.0",
        granted_at: DateTime.utc_now()
      }

      v2_attrs = %{
        user_id: user.id,
        consent_type: "privacy_policy",
        version: "2.0",
        granted_at: DateTime.utc_now()
      }

      changeset1 = UserConsent.grant_changeset(%UserConsent{}, v1_attrs)
      assert {:ok, _consent1} = Repo.insert(changeset1)

      changeset2 = UserConsent.grant_changeset(%UserConsent{}, v2_attrs)
      assert {:ok, _consent2} = Repo.insert(changeset2)
    end
  end

  describe "revoke_changeset/2" do
    setup do
      user = user_fixture()
      granted_at = DateTime.utc_now()

      consent =
        %UserConsent{}
        |> UserConsent.grant_changeset(%{
          user_id: user.id,
          consent_type: "privacy_policy",
          version: "1.0",
          granted_at: granted_at
        })
        |> Repo.insert!()

      {:ok, user: user, consent: consent}
    end

    test "valid attributes create valid changeset", %{consent: consent} do
      attrs = %{revoked_at: DateTime.utc_now()}
      changeset = UserConsent.revoke_changeset(consent, attrs)
      assert changeset.valid?
    end

    test "requires revoked_at", %{consent: consent} do
      changeset = UserConsent.revoke_changeset(consent, %{})
      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).revoked_at
    end

    test "prevents revoking already revoked consent", %{consent: consent} do
      # Revoke once
      revoked_at = DateTime.utc_now()

      {:ok, revoked_consent} =
        consent
        |> UserConsent.revoke_changeset(%{revoked_at: revoked_at})
        |> Repo.update()

      # Try to revoke again
      changeset = UserConsent.revoke_changeset(revoked_consent, %{revoked_at: DateTime.utc_now()})
      refute changeset.valid?
      assert "consent already revoked" in errors_on(changeset).revoked_at
    end

    test "successfully revokes consent", %{consent: consent} do
      revoked_at = DateTime.truncate(DateTime.utc_now(), :second)

      changeset = UserConsent.revoke_changeset(consent, %{revoked_at: revoked_at})
      assert {:ok, updated_consent} = Repo.update(changeset)
      assert updated_consent.revoked_at == revoked_at
    end
  end
end
