defmodule MvWeb.RoleLiveTest do
  @moduledoc """
  Tests for role management LiveViews.
  """
  use MvWeb.ConnCase, async: false

  import Phoenix.LiveViewTest

  alias Mv.Authorization
  alias Mv.Authorization.Role

  # Helper to create a role (authorize?: false for test data setup)
  defp create_role(attrs \\ %{}) do
    default_attrs = %{
      name: "Test Role #{System.unique_integer([:positive])}",
      description: "Test description",
      permission_set_name: "read_only"
    }

    attrs = Map.merge(default_attrs, attrs)

    case Authorization.create_role(attrs, authorize?: false) do
      {:ok, role} -> role
      {:error, error} -> raise "Failed to create role: #{inspect(error)}"
    end
  end

  # Helper to create admin user with admin role
  defp create_admin_user(conn, actor) do
    # Create admin role
    admin_role =
      case Authorization.list_roles(authorize?: false) do
        {:ok, roles} ->
          case Enum.find(roles, &(&1.name == "Admin")) do
            nil ->
              # Create admin role if it doesn't exist
              create_role(%{
                name: "Admin",
                description: "Administrator with full access",
                permission_set_name: "admin"
              })

            role ->
              role
          end

        _ ->
          # Create admin role if list_roles fails
          create_role(%{
            name: "Admin",
            description: "Administrator with full access",
            permission_set_name: "admin"
          })
      end

    # Create user
    {:ok, user} =
      Mv.Accounts.User
      |> Ash.Changeset.for_create(:register_with_password, %{
        email: "admin#{System.unique_integer([:positive])}@mv.local",
        password: "testpassword123"
      })
      |> Ash.create(actor: actor)

    # Assign admin role using manage_relationship
    {:ok, user} =
      user
      |> Ash.Changeset.for_update(:update, %{})
      |> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
      |> Ash.update(actor: actor)

    # Load role for authorization checks (must be loaded for can?/3 to work)
    user_with_role = Ash.load!(user, :role, domain: Mv.Accounts, actor: actor)

    # Store user with role in session for LiveView
    conn = conn_with_password_user(conn, user_with_role)
    {conn, user_with_role, admin_role}
  end

  # Helper to create non-admin user
  defp create_non_admin_user(conn, actor) do
    {:ok, user} =
      Mv.Accounts.User
      |> Ash.Changeset.for_create(:register_with_password, %{
        email: "user#{System.unique_integer([:positive])}@mv.local",
        password: "testpassword123"
      })
      |> Ash.create(actor: actor)

    conn = conn_with_password_user(conn, user)
    {conn, user}
  end

  describe "index page" do
    setup %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      {conn, user, _admin_role} = create_admin_user(conn, system_actor)
      %{conn: conn, actor: system_actor, user: user}
    end

    @tag :slow
    test "mounts successfully", %{conn: conn} do
      {:ok, _view, _html} = live(conn, "/admin/roles")
    end

    test "loads all roles from database", %{conn: conn} do
      role1 = create_role(%{name: "Role 1"})
      role2 = create_role(%{name: "Role 2"})

      {:ok, _view, html} = live(conn, "/admin/roles")

      assert html =~ role1.name
      assert html =~ role2.name
    end

    test "shows table with role names", %{conn: conn} do
      role = create_role(%{name: "Test Role"})

      {:ok, _view, html} = live(conn, "/admin/roles")

      assert html =~ role.name
      assert html =~ role.description
      assert html =~ role.permission_set_name
    end

    test "shows system role badge", %{conn: conn, actor: actor} do
      _system_role =
        Role
        |> Ash.Changeset.for_create(:create_role, %{
          name: "System Role",
          permission_set_name: "own_data"
        })
        |> Ash.Changeset.force_change_attribute(:is_system_role, true)
        |> Ash.create!(actor: actor)

      {:ok, _view, html} = live(conn, "/admin/roles")

      assert html =~ "System Role" || html =~ "system"
    end

    test "delete button disabled for system roles", %{conn: conn, actor: actor} do
      system_role =
        Role
        |> Ash.Changeset.for_create(:create_role, %{
          name: "System Role",
          permission_set_name: "own_data"
        })
        |> Ash.Changeset.force_change_attribute(:is_system_role, true)
        |> Ash.create!(actor: actor)

      {:ok, view, _html} = live(conn, "/admin/roles")

      assert has_element?(
               view,
               "button[phx-click='delete'][phx-value-id='#{system_role.id}'][disabled]"
             ) ||
               not has_element?(
                 view,
                 "button[phx-click='delete'][phx-value-id='#{system_role.id}']"
               )
    end

    test "delete button enabled for non-system roles", %{conn: conn} do
      role = create_role()

      {:ok, view, html} = live(conn, "/admin/roles")

      # Delete is a link with phx-click containing delete event
      # Check if delete link exists in HTML (phx-click contains delete and role id)
      assert (html =~ "phx-click" && html =~ "delete" && html =~ role.id) ||
               has_element?(view, "a[phx-click*='delete'][phx-value-id='#{role.id}']") ||
               has_element?(view, "a[aria-label='Delete role']")
    end

    test "new role button navigates to form", %{conn: conn} do
      {:ok, view, html} = live(conn, "/admin/roles")

      # Check if button exists (admin should see it)
      if html =~ "New Role" do
        {:error, {:live_redirect, %{to: to}}} =
          view
          |> element("a[href='/admin/roles/new'], button[href='/admin/roles/new']")
          |> render_click()

        assert to == "/admin/roles/new"
      else
        # If button not visible, user doesn't have permission (expected for non-admin)
        # This test assumes admin user, so button should be visible
        flunk("New Role button not found - user may not have admin role loaded")
      end
    end
  end

  describe "show page" do
    setup %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      {conn, user, _admin_role} = create_admin_user(conn, system_actor)
      %{conn: conn, actor: system_actor, user: user}
    end

    test "mounts with valid role ID", %{conn: conn} do
      role = create_role()

      {:ok, _view, html} = live(conn, "/admin/roles/#{role.id}")

      assert html =~ role.name
      assert html =~ role.description
      assert html =~ role.permission_set_name
    end

    test "returns 404 for invalid role ID", %{conn: conn} do
      invalid_id = Ecto.UUID.generate()

      # Should redirect to index with error message
      # redirect in mount returns {:error, {:redirect, ...}}
      result = live(conn, "/admin/roles/#{invalid_id}")

      assert match?({:error, {:redirect, %{to: "/admin/roles"}}}, result)
    end

    test "shows system role badge if is_system_role is true", %{conn: conn, actor: actor} do
      system_role =
        Role
        |> Ash.Changeset.for_create(:create_role, %{
          name: "System Role",
          permission_set_name: "own_data"
        })
        |> Ash.Changeset.force_change_attribute(:is_system_role, true)
        |> Ash.create!(actor: actor)

      {:ok, _view, html} = live(conn, "/admin/roles/#{system_role.id}")

      assert html =~ "System Role" || html =~ "system"
    end
  end

  describe "form - create" do
    setup %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      {conn, user, _admin_role} = create_admin_user(conn, system_actor)
      %{conn: conn, actor: system_actor, user: user}
    end

    test "mounts successfully", %{conn: conn} do
      {:ok, _view, _html} = live(conn, "/admin/roles/new")
    end

    test "form dropdown shows all 4 permission sets", %{conn: conn} do
      {:ok, _view, html} = live(conn, "/admin/roles/new")

      assert html =~ "own_data"
      assert html =~ "read_only"
      assert html =~ "normal_user"
      assert html =~ "admin"
    end

    test "creates new role with valid data", %{conn: conn} do
      {:ok, view, _html} = live(conn, "/admin/roles/new")

      attrs = %{
        "name" => "New Role",
        "description" => "New description",
        "permission_set_name" => "read_only"
      }

      view
      |> form("#role-form", role: attrs)
      |> render_submit()

      # Should redirect to index or show page
      assert_redirect(view, "/admin/roles")
    end

    test "shows error with invalid permission_set_name", %{conn: conn} do
      {:ok, view, _html} = live(conn, "/admin/roles/new")

      # Try to submit with empty permission_set_name (invalid)
      attrs = %{
        "name" => "New Role",
        "description" => "New description",
        "permission_set_name" => ""
      }

      view
      |> form("#role-form", role: attrs)
      |> render_submit()

      # Should show validation error
      html = render(view)
      assert html =~ "error" || html =~ "required" || html =~ "Permission Set"
    end

    test "shows flash message after successful creation", %{conn: conn} do
      {:ok, view, _html} = live(conn, "/admin/roles/new")

      attrs = %{
        "name" => "New Role #{System.unique_integer([:positive])}",
        "description" => "New description",
        "permission_set_name" => "read_only"
      }

      view
      |> form("#role-form", role: attrs)
      |> render_submit()

      # Should redirect to index
      assert_redirect(view, "/admin/roles")
    end
  end

  describe "form - edit" do
    setup %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      {conn, user, _admin_role} = create_admin_user(conn, system_actor)
      role = create_role()
      %{conn: conn, actor: system_actor, user: user, role: role}
    end

    test "mounts with valid role ID", %{conn: conn, role: role} do
      {:ok, _view, html} = live(conn, "/admin/roles/#{role.id}/edit")

      assert html =~ role.name
    end

    test "returns 404 for invalid role ID in edit", %{conn: conn} do
      invalid_id = Ecto.UUID.generate()

      # Should redirect to index with error message
      # redirect in mount returns {:error, {:redirect, ...}}
      result = live(conn, "/admin/roles/#{invalid_id}/edit")

      assert match?({:error, {:redirect, %{to: "/admin/roles"}}}, result)
    end

    test "updates role name", %{conn: conn, role: role, actor: actor} do
      {:ok, view, _html} = live(conn, "/admin/roles/#{role.id}/edit?return_to=show")

      attrs = %{
        "name" => "Updated Role Name",
        "description" => role.description,
        "permission_set_name" => role.permission_set_name
      }

      view
      |> form("#role-form", role: attrs)
      |> render_submit()

      assert_redirect(view, "/admin/roles/#{role.id}")

      # Verify update
      {:ok, updated_role} = Authorization.get_role(role.id, actor: actor)
      assert updated_role.name == "Updated Role Name"
    end

    test "updates system role's permission_set_name", %{conn: conn, actor: actor} do
      system_role =
        Role
        |> Ash.Changeset.for_create(:create_role, %{
          name: "System Role",
          permission_set_name: "own_data"
        })
        |> Ash.Changeset.force_change_attribute(:is_system_role, true)
        |> Ash.create!(actor: actor)

      {:ok, view, _html} = live(conn, "/admin/roles/#{system_role.id}/edit?return_to=show")

      attrs = %{
        "name" => system_role.name,
        "description" => system_role.description,
        "permission_set_name" => "read_only"
      }

      view
      |> form("#role-form", role: attrs)
      |> render_submit()

      assert_redirect(view, "/admin/roles/#{system_role.id}")

      # Verify update
      {:ok, updated_role} = Authorization.get_role(system_role.id, actor: actor)
      assert updated_role.permission_set_name == "read_only"
    end
  end

  describe "delete functionality" do
    setup %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      {conn, user, _admin_role} = create_admin_user(conn, system_actor)
      %{conn: conn, actor: system_actor, user: user}
    end

    @tag :slow
    test "deletes non-system role", %{conn: conn, actor: actor} do
      role = create_role()

      {:ok, view, html} = live(conn, "/admin/roles")

      # Delete is a link - JS.push creates phx-click with value containing id
      # Verify the role id is in the HTML (in phx-click value)
      assert html =~ role.id

      # Send delete event directly to avoid selector issues with multiple delete buttons
      render_click(view, "delete", %{"id" => role.id})

      # Verify deletion by checking database
      assert {:error, %Ash.Error.Invalid{errors: [%Ash.Error.Query.NotFound{}]}} =
               Authorization.get_role(role.id, actor: actor)
    end

    test "fails to delete system role with error message", %{conn: conn, actor: actor} do
      system_role =
        Role
        |> Ash.Changeset.for_create(:create_role, %{
          name: "System Role",
          permission_set_name: "own_data"
        })
        |> Ash.Changeset.force_change_attribute(:is_system_role, true)
        |> Ash.create!(actor: actor)

      {:ok, view, html} = live(conn, "/admin/roles")

      # System role delete button should be disabled
      assert html =~ "disabled" || html =~ "cursor-not-allowed" ||
               html =~ "System roles cannot be deleted"

      # Try to delete via event (backend check)
      render_click(view, "delete", %{"id" => system_role.id})

      # Should show error message
      assert render(view) =~ "System roles cannot be deleted"

      # Role should still exist
      {:ok, _role} = Authorization.get_role(system_role.id, actor: actor)
    end
  end

  describe "authorization" do
    setup do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      %{actor: system_actor}
    end

    test "only admin can access /admin/roles", %{conn: conn, actor: actor} do
      {conn, user} = create_non_admin_user(conn, actor)

      # Non-admin (no role or non-admin role) is redirected by CheckPagePermission plug
      assert {:error, {:redirect, %{to: to}}} = live(conn, "/admin/roles")
      assert to == "/users/#{user.id}"
    end

    test "admin can access /admin/roles", %{conn: conn, actor: actor} do
      {conn, _user, _admin_role} = create_admin_user(conn, actor)

      {:ok, _view, _html} = live(conn, "/admin/roles")
    end
  end
end
