defmodule Mv.ReleaseTest do
  @moduledoc """
  Tests for release tasks (e.g. seed_admin/0).

  These tests verify that the admin user is created or updated from ENV
  (ADMIN_EMAIL, ADMIN_PASSWORD / ADMIN_PASSWORD_FILE) in an idempotent way.
  """
  use Mv.DataCase, async: false

  alias Mv.Accounts
  alias Mv.Accounts.User
  alias Mv.Authorization.Role

  require Ash.Query

  setup do
    ensure_admin_role_exists()
    clear_admin_env()
    :ok
  end

  describe "seed_admin/0" do
    test "without ADMIN_EMAIL does nothing (idempotent), no user created" do
      clear_admin_env()
      user_count_before = count_users()

      Mv.Release.seed_admin()

      assert count_users() == user_count_before
    end

    test "with ADMIN_EMAIL but without ADMIN_PASSWORD and user does not exist: does not create user" do
      System.delete_env("ADMIN_PASSWORD")
      System.delete_env("ADMIN_PASSWORD_FILE")

      email = "admin-no-password-#{System.unique_integer([:positive])}@test.example.com"
      System.put_env("ADMIN_EMAIL", email)
      on_exit(fn -> System.delete_env("ADMIN_EMAIL") end)

      user_count_before = count_users()
      Mv.Release.seed_admin()

      assert count_users() == user_count_before,
             "seed_admin must not create any user when ADMIN_PASSWORD is unset (expected #{user_count_before}, got #{count_users()})"
    end

    test "with ADMIN_EMAIL but without ADMIN_PASSWORD and user exists: sets Admin role (OIDC-only bootstrap)" do
      System.delete_env("ADMIN_PASSWORD")
      System.delete_env("ADMIN_PASSWORD_FILE")

      email = "existing-admin-#{System.unique_integer([:positive])}@test.example.com"
      System.put_env("ADMIN_EMAIL", email)
      on_exit(fn -> System.delete_env("ADMIN_EMAIL") end)

      {:ok, _user} = create_user_with_mitglied_role(email)

      Mv.Release.seed_admin()

      {:ok, updated} = get_user_by_email(email)
      assert updated.role_id == admin_role_id()
    end

    test "with ADMIN_EMAIL and ADMIN_PASSWORD: creates user with Admin role and sets password" do
      email = "new-admin-#{System.unique_integer([:positive])}@test.example.com"
      password = "SecurePassword123!"
      System.put_env("ADMIN_EMAIL", email)
      System.put_env("ADMIN_PASSWORD", password)

      on_exit(fn ->
        System.delete_env("ADMIN_EMAIL")
        System.delete_env("ADMIN_PASSWORD")
      end)

      Mv.Release.seed_admin()

      assert user_exists?(email),
             "seed_admin must create user when ADMIN_EMAIL and ADMIN_PASSWORD are set"

      {:ok, user} = get_user_by_email(email)
      assert user.role_id == admin_role_id()
      assert user.hashed_password != nil
      assert AshAuthentication.BcryptProvider.valid?(password, user.hashed_password)
    end

    test "with ADMIN_EMAIL and ADMIN_PASSWORD, user already exists: assigns Admin role and updates password" do
      email = "existing-to-admin-#{System.unique_integer([:positive])}@test.example.com"
      password = "NewSecurePassword456!"
      System.put_env("ADMIN_EMAIL", email)
      System.put_env("ADMIN_PASSWORD", password)

      on_exit(fn ->
        System.delete_env("ADMIN_EMAIL")
        System.delete_env("ADMIN_PASSWORD")
      end)

      {:ok, user} = create_user_with_mitglied_role(email)
      assert user.role_id == mitglied_role_id()
      old_hashed = user.hashed_password

      Mv.Release.seed_admin()

      {:ok, updated} = get_user_by_email(email)
      assert updated.role_id == admin_role_id()
      assert updated.hashed_password != nil
      assert updated.hashed_password != old_hashed
      assert AshAuthentication.BcryptProvider.valid?(password, updated.hashed_password)
    end

    test "with ADMIN_PASSWORD_FILE: reads password from file, same behavior as ADMIN_PASSWORD" do
      email = "admin-file-#{System.unique_integer([:positive])}@test.example.com"
      password = "FilePassword789!"

      tmp =
        Path.join(
          System.tmp_dir!(),
          "mv_admin_password_#{System.unique_integer([:positive])}.txt"
        )

      File.write!(tmp, password)
      System.put_env("ADMIN_EMAIL", email)
      System.put_env("ADMIN_PASSWORD_FILE", tmp)

      on_exit(fn ->
        System.delete_env("ADMIN_EMAIL")
        System.delete_env("ADMIN_PASSWORD_FILE")
        File.rm(tmp)
      end)

      Mv.Release.seed_admin()

      assert user_exists?(email), "seed_admin must create user when ADMIN_PASSWORD_FILE is set"
      {:ok, user} = get_user_by_email(email)
      assert AshAuthentication.BcryptProvider.valid?(password, user.hashed_password)
    end

    test "called twice: idempotent (no duplicate user, same state)" do
      email = "idempotent-admin-#{System.unique_integer([:positive])}@test.example.com"
      password = "IdempotentPassword123!"
      System.put_env("ADMIN_EMAIL", email)
      System.put_env("ADMIN_PASSWORD", password)

      on_exit(fn ->
        System.delete_env("ADMIN_EMAIL")
        System.delete_env("ADMIN_PASSWORD")
      end)

      Mv.Release.seed_admin()
      {:ok, user_after_first} = get_user_by_email(email)
      user_count_after_first = count_users()

      Mv.Release.seed_admin()

      assert count_users() == user_count_after_first
      {:ok, user_after_second} = get_user_by_email(email)
      assert user_after_second.id == user_after_first.id
      assert user_after_second.role_id == admin_role_id()
    end
  end

  defp clear_admin_env do
    System.delete_env("ADMIN_EMAIL")
    System.delete_env("ADMIN_PASSWORD")
    System.delete_env("ADMIN_PASSWORD_FILE")
  end

  defp ensure_admin_role_exists do
    case Role
         |> Ash.Query.filter(name == "Admin")
         |> Ash.read_one(authorize?: false, domain: Mv.Authorization) do
      {:ok, nil} ->
        Role
        |> Ash.Changeset.for_create(:create_role_with_system_flag, %{
          name: "Admin",
          description: "Administrator with full access",
          permission_set_name: "admin",
          is_system_role: false
        })
        |> Ash.create!(authorize?: false, domain: Mv.Authorization)

      _ ->
        :ok
    end
  end

  defp admin_role_id do
    {:ok, role} =
      Role
      |> Ash.Query.filter(name == "Admin")
      |> Ash.read_one(authorize?: false, domain: Mv.Authorization)

    role.id
  end

  defp mitglied_role_id do
    {:ok, role} = Role.get_mitglied_role()
    role.id
  end

  defp count_users do
    User
    |> Ash.read!(authorize?: false, domain: Mv.Accounts)
    |> length()
  end

  defp user_exists?(email) do
    case get_user_by_email(email) do
      {:ok, _} -> true
      {:error, _} -> false
    end
  end

  defp get_user_by_email(email) do
    User
    |> Ash.Query.filter(email == ^email)
    |> Ash.read_one(authorize?: false, domain: Mv.Accounts)
  end

  defp create_user_with_mitglied_role(email) do
    {:ok, _} = Accounts.create_user(%{email: email}, authorize?: false)
    get_user_by_email(email)
  end
end
