defmodule Towerops.Workers.LoginHistoryCleanupWorkerTest do
  use Towerops.DataCase, async: true

  alias Towerops.Accounts.LoginAttempt
  alias Towerops.Workers.LoginHistoryCleanupWorker

  describe "perform/1" do
    test "deletes active login attempts older than retention period" do
      # Create an old active record (400 days old, past default 365 retention)
      old_attempt =
        insert_login_attempt(%{
          inserted_at: DateTime.add(DateTime.utc_now(), -400, :day),
          anonymized_at: nil
        })

      # Execute the worker
      assert {:ok, %{deleted: 1, anonymized_deleted: 0}} =
               LoginHistoryCleanupWorker.perform(%Oban.Job{args: %{}})

      # Verify old active record was deleted
      refute Repo.get(LoginAttempt, old_attempt.id)
    end

    test "keeps active login attempts within retention period" do
      # Create a recent active record (100 days old, within default 365 retention)
      recent_attempt =
        insert_login_attempt(%{
          inserted_at: DateTime.add(DateTime.utc_now(), -100, :day),
          anonymized_at: nil
        })

      # Execute the worker
      assert {:ok, %{deleted: 0, anonymized_deleted: 0}} =
               LoginHistoryCleanupWorker.perform(%Oban.Job{args: %{}})

      # Verify recent active record still exists
      assert Repo.get(LoginAttempt, recent_attempt.id)
    end

    test "deletes anonymized records older than 90 days" do
      # Create an anonymized record (100 days after anonymization)
      anon_date = DateTime.add(DateTime.utc_now(), -100, :day)

      old_anon_attempt =
        insert_login_attempt(%{
          inserted_at: DateTime.add(anon_date, -200, :day),
          anonymized_at: anon_date
        })

      # Execute the worker
      assert {:ok, %{deleted: 0, anonymized_deleted: 1}} =
               LoginHistoryCleanupWorker.perform(%Oban.Job{args: %{}})

      # Verify old anonymized record was deleted
      refute Repo.get(LoginAttempt, old_anon_attempt.id)
    end

    test "keeps anonymized records within 90 day period" do
      # Create an anonymized record (30 days after anonymization)
      anon_date = DateTime.add(DateTime.utc_now(), -30, :day)

      recent_anon_attempt =
        insert_login_attempt(%{
          inserted_at: DateTime.add(anon_date, -200, :day),
          anonymized_at: anon_date
        })

      # Execute the worker
      assert {:ok, %{deleted: 0, anonymized_deleted: 0}} =
               LoginHistoryCleanupWorker.perform(%Oban.Job{args: %{}})

      # Verify recent anonymized record still exists
      assert Repo.get(LoginAttempt, recent_anon_attempt.id)
    end

    test "handles mixed active and anonymized records" do
      # Create old active record (should be deleted)
      old_active =
        insert_login_attempt(%{
          inserted_at: DateTime.add(DateTime.utc_now(), -400, :day),
          anonymized_at: nil
        })

      # Create recent active record (should be kept)
      recent_active =
        insert_login_attempt(%{
          inserted_at: DateTime.add(DateTime.utc_now(), -100, :day),
          anonymized_at: nil
        })

      # Create old anonymized record (should be deleted)
      old_anon =
        insert_login_attempt(%{
          inserted_at: DateTime.add(DateTime.utc_now(), -500, :day),
          anonymized_at: DateTime.add(DateTime.utc_now(), -100, :day)
        })

      # Create recent anonymized record (should be kept)
      recent_anon =
        insert_login_attempt(%{
          inserted_at: DateTime.add(DateTime.utc_now(), -400, :day),
          anonymized_at: DateTime.add(DateTime.utc_now(), -30, :day)
        })

      # Execute the worker
      assert {:ok, %{deleted: 1, anonymized_deleted: 1}} =
               LoginHistoryCleanupWorker.perform(%Oban.Job{args: %{}})

      # Verify old records were deleted
      refute Repo.get(LoginAttempt, old_active.id)
      refute Repo.get(LoginAttempt, old_anon.id)

      # Verify recent records still exist
      assert Repo.get(LoginAttempt, recent_active.id)
      assert Repo.get(LoginAttempt, recent_anon.id)
    end

    test "returns zero counts when no records need deletion" do
      # Create only recent records
      insert_login_attempt(%{
        inserted_at: DateTime.add(DateTime.utc_now(), -10, :day),
        anonymized_at: nil
      })

      # Execute the worker
      assert {:ok, %{deleted: 0, anonymized_deleted: 0}} =
               LoginHistoryCleanupWorker.perform(%Oban.Job{args: %{}})
    end

    test "works when no records exist at all" do
      # Execute the worker with no records in the database
      assert {:ok, %{deleted: 0, anonymized_deleted: 0}} =
               LoginHistoryCleanupWorker.perform(%Oban.Job{args: %{}})
    end

    test "respects custom retention period from config" do
      # Temporarily override config (if Application.put_env works in tests)
      # Otherwise, this test documents expected behavior

      # Create a record 200 days old (would be deleted if retention is 180 days)
      old_attempt =
        insert_login_attempt(%{
          inserted_at: DateTime.add(DateTime.utc_now(), -200, :day),
          anonymized_at: nil
        })

      # With default 365 day retention, this should NOT be deleted
      assert {:ok, %{deleted: 0, anonymized_deleted: 0}} =
               LoginHistoryCleanupWorker.perform(%Oban.Job{args: %{}})

      # Verify record still exists
      assert Repo.get(LoginAttempt, old_attempt.id)
    end

    test "handles boundary case at exactly retention cutoff" do
      # Create a record at exactly 365 days old
      cutoff_date = DateTime.add(DateTime.utc_now(), -365, :day)

      boundary_attempt =
        insert_login_attempt(%{
          inserted_at: cutoff_date,
          anonymized_at: nil
        })

      # Execute the worker
      assert {:ok, %{deleted: count, anonymized_deleted: _}} =
               LoginHistoryCleanupWorker.perform(%Oban.Job{args: %{}})

      # The query uses `<` not `<=`, so exactly at cutoff should NOT be deleted
      assert count == 0
      assert Repo.get(LoginAttempt, boundary_attempt.id)
    end

    test "handles boundary case for anonymized at exactly 90 days" do
      # Create an anonymized record at exactly 90 days
      anon_date = DateTime.add(DateTime.utc_now(), -90, :day)

      boundary_anon =
        insert_login_attempt(%{
          inserted_at: DateTime.add(anon_date, -100, :day),
          anonymized_at: anon_date
        })

      # Execute the worker
      assert {:ok, %{deleted: _, anonymized_deleted: anon_count}} =
               LoginHistoryCleanupWorker.perform(%Oban.Job{args: %{}})

      # The query uses `<` not `<=`, so exactly at 90 days should NOT be deleted
      assert anon_count == 0
      assert Repo.get(LoginAttempt, boundary_anon.id)
    end
  end

  # Helper function to create login attempts
  defp insert_login_attempt(attrs) do
    default_attrs = %{
      email: "test#{System.unique_integer()}@example.com",
      success: true,
      method: "password",
      ip_address: "192.168.1.1"
    }

    attrs =
      default_attrs
      |> Map.merge(attrs)
      |> Map.update(:inserted_at, DateTime.utc_now(), fn dt -> dt end)

    # Truncate datetime values to remove microseconds (Ecto requirement)
    inserted_at = attrs |> Map.get(:inserted_at) |> DateTime.truncate(:second)

    anonymized_at =
      case Map.get(attrs, :anonymized_at) do
        nil -> nil
        dt -> DateTime.truncate(dt, :second)
      end

    # Remove timestamp fields from attrs (not castable)
    attrs_for_changeset =
      attrs
      |> Map.delete(:inserted_at)
      |> Map.delete(:anonymized_at)

    changeset =
      %LoginAttempt{}
      |> LoginAttempt.changeset(attrs_for_changeset)
      |> Ecto.Changeset.put_change(:inserted_at, inserted_at)

    # Add anonymized_at if provided
    changeset =
      if anonymized_at do
        Ecto.Changeset.put_change(changeset, :anonymized_at, anonymized_at)
      else
        changeset
      end

    Repo.insert!(changeset)
  end
end
