defmodule ToweropsWeb.PermissionsTest do
  use Towerops.DataCase, async: true

  import Towerops.AccountsFixtures
  import Towerops.OrganizationsFixtures

  alias Phoenix.LiveView.Socket
  alias Towerops.Accounts.Scope
  alias Towerops.Repo
  alias ToweropsWeb.Permissions

  describe "can?/3 with superuser" do
    setup do
      user = user_fixture(%{is_superuser: true})
      organization = organization_fixture(user.id)
      organization = Repo.preload(organization, :memberships)

      scope =
        user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      %{scope: scope, user: user, organization: organization}
    end

    test "superuser can perform any action", %{scope: scope} do
      # Test various actions and resources
      assert Permissions.can?(scope, :delete, :organization)
      assert Permissions.can?(scope, :delete, :device)
      assert Permissions.can?(scope, :edit, :device)
      assert Permissions.can?(scope, :create, :device)
      assert Permissions.can?(scope, :delete, :backup)
      assert Permissions.can?(scope, :manage, :anything)
    end

    test "superuser can? works with Socket", %{scope: scope} do
      socket = %Socket{assigns: %{current_scope: scope}}

      assert Permissions.can?(socket, :delete, :organization)
      assert Permissions.can?(socket, :delete, :device)
    end
  end

  describe "can?/3 with owner role" do
    setup do
      user = user_fixture()
      organization = organization_fixture(user.id)
      organization = Repo.preload(organization, :memberships)

      scope =
        user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      %{scope: scope, user: user, organization: organization}
    end

    test "owner can perform any action", %{scope: scope} do
      assert Permissions.can?(scope, :delete, :organization)
      assert Permissions.can?(scope, :delete, :device)
      assert Permissions.can?(scope, :edit, :device)
      assert Permissions.can?(scope, :create, :device)
      assert Permissions.can?(scope, :delete, :backup)
      assert Permissions.can?(scope, :create, :invitation)
    end
  end

  describe "can?/3 with admin role" do
    setup do
      owner = user_fixture()
      organization = organization_fixture(owner.id)

      admin_user = user_fixture()
      membership_fixture(organization.id, admin_user.id, :admin)

      organization = Repo.preload(organization, :memberships, force: true)

      scope =
        admin_user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      %{scope: scope, user: admin_user, organization: organization}
    end

    test "admin can perform most actions", %{scope: scope} do
      assert Permissions.can?(scope, :edit, :device)
      assert Permissions.can?(scope, :create, :device)
      assert Permissions.can?(scope, :delete, :device)
      assert Permissions.can?(scope, :delete, :backup)
      assert Permissions.can?(scope, :create, :invitation)
      assert Permissions.can?(scope, :view, :organization)
    end

    test "admin cannot delete organization", %{scope: scope} do
      refute Permissions.can?(scope, :delete, :organization)
    end
  end

  describe "can?/3 with member role" do
    setup do
      owner = user_fixture()
      organization = organization_fixture(owner.id)

      member_user = user_fixture()
      membership_fixture(organization.id, member_user.id, :member)

      organization = Repo.preload(organization, :memberships, force: true)

      scope =
        member_user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      %{scope: scope, user: member_user, organization: organization}
    end

    test "member can view and edit devices", %{scope: scope} do
      assert Permissions.can?(scope, :view, :device)
      assert Permissions.can?(scope, :list, :device)
      assert Permissions.can?(scope, :create, :device)
      assert Permissions.can?(scope, :edit, :device)
    end

    test "member can view and edit sites", %{scope: scope} do
      assert Permissions.can?(scope, :view, :site)
      assert Permissions.can?(scope, :list, :site)
      assert Permissions.can?(scope, :create, :site)
      assert Permissions.can?(scope, :edit, :site)
    end

    test "member can view and edit alerts", %{scope: scope} do
      assert Permissions.can?(scope, :view, :alert)
      assert Permissions.can?(scope, :list, :alert)
      assert Permissions.can?(scope, :create, :alert)
      assert Permissions.can?(scope, :edit, :alert)
    end

    test "member cannot delete anything", %{scope: scope} do
      refute Permissions.can?(scope, :delete, :device)
      refute Permissions.can?(scope, :delete, :site)
      refute Permissions.can?(scope, :delete, :alert)
      refute Permissions.can?(scope, :delete, :backup)
      refute Permissions.can?(scope, :delete, :organization)
    end

    test "member cannot manage memberships", %{scope: scope} do
      refute Permissions.can?(scope, :create, :membership)
      refute Permissions.can?(scope, :edit, :membership)
    end

    test "member cannot manage invitations", %{scope: scope} do
      refute Permissions.can?(scope, :create, :invitation)
      refute Permissions.can?(scope, :delete, :invitation)
    end

    test "member can view organization", %{scope: scope} do
      assert Permissions.can?(scope, :view, :organization)
      assert Permissions.can?(scope, :list, :organization)
    end
  end

  describe "can?/3 with viewer role" do
    setup do
      owner = user_fixture()
      organization = organization_fixture(owner.id)

      viewer_user = user_fixture()
      membership_fixture(organization.id, viewer_user.id, :viewer)

      organization = Repo.preload(organization, :memberships, force: true)

      scope =
        viewer_user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      %{scope: scope, user: viewer_user, organization: organization}
    end

    test "viewer can only view resources", %{scope: scope} do
      assert Permissions.can?(scope, :view, :device)
      assert Permissions.can?(scope, :list, :device)
      assert Permissions.can?(scope, :view, :site)
      assert Permissions.can?(scope, :list, :site)
      assert Permissions.can?(scope, :view, :organization)
    end

    test "viewer cannot perform any write actions", %{scope: scope} do
      refute Permissions.can?(scope, :create, :device)
      refute Permissions.can?(scope, :edit, :device)
      refute Permissions.can?(scope, :delete, :device)
      refute Permissions.can?(scope, :create, :site)
      refute Permissions.can?(scope, :edit, :site)
      refute Permissions.can?(scope, :delete, :site)
    end
  end

  describe "can?/3 with no organization context" do
    test "returns false when scope has no organization" do
      user = user_fixture()
      scope = Scope.for_user(user)

      refute Permissions.can?(scope, :view, :device)
      refute Permissions.can?(scope, :edit, :device)
      refute Permissions.can?(scope, :delete, :device)
    end
  end

  describe "can?/3 with nil scope" do
    test "returns false for nil scope" do
      refute Permissions.can?(nil, :view, :device)
      refute Permissions.can?(nil, :edit, :device)
      refute Permissions.can?(nil, :delete, :device)
    end
  end

  describe "owner?/1 with various roles" do
    test "returns true for superuser" do
      user = user_fixture(%{is_superuser: true})
      organization = organization_fixture(user.id)
      organization = Repo.preload(organization, :memberships)

      scope =
        user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      assert Permissions.owner?(scope)
    end

    test "returns true for owner role" do
      user = user_fixture()
      organization = organization_fixture(user.id)
      organization = Repo.preload(organization, :memberships)

      scope =
        user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      assert Permissions.owner?(scope)
    end

    test "returns false for admin role" do
      owner = user_fixture()
      organization = organization_fixture(owner.id)

      admin_user = user_fixture()
      membership_fixture(organization.id, admin_user.id, :admin)

      organization = Repo.preload(organization, :memberships, force: true)

      scope =
        admin_user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      refute Permissions.owner?(scope)
    end

    test "returns false for member role" do
      owner = user_fixture()
      organization = organization_fixture(owner.id)

      member_user = user_fixture()
      membership_fixture(organization.id, member_user.id, :member)

      organization = Repo.preload(organization, :memberships, force: true)

      scope =
        member_user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      refute Permissions.owner?(scope)
    end

    test "returns false for viewer role" do
      owner = user_fixture()
      organization = organization_fixture(owner.id)

      viewer_user = user_fixture()
      membership_fixture(organization.id, viewer_user.id, :viewer)

      organization = Repo.preload(organization, :memberships, force: true)

      scope =
        viewer_user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      refute Permissions.owner?(scope)
    end

    test "returns false when no organization context" do
      user = user_fixture()
      scope = Scope.for_user(user)

      refute Permissions.owner?(scope)
    end

    test "returns false for nil scope" do
      refute Permissions.owner?(nil)
    end

    test "works with Socket" do
      user = user_fixture()
      organization = organization_fixture(user.id)
      organization = Repo.preload(organization, :memberships)

      scope =
        user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      socket = %Socket{assigns: %{current_scope: scope}}

      assert Permissions.owner?(socket)
    end
  end

  describe "admin?/1 with various roles" do
    test "returns true for superuser" do
      user = user_fixture(%{is_superuser: true})
      organization = organization_fixture(user.id)
      organization = Repo.preload(organization, :memberships)

      scope =
        user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      assert Permissions.admin?(scope)
    end

    test "returns true for owner role" do
      user = user_fixture()
      organization = organization_fixture(user.id)
      organization = Repo.preload(organization, :memberships)

      scope =
        user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      assert Permissions.admin?(scope)
    end

    test "returns true for admin role" do
      owner = user_fixture()
      organization = organization_fixture(owner.id)

      admin_user = user_fixture()
      membership_fixture(organization.id, admin_user.id, :admin)

      organization = Repo.preload(organization, :memberships, force: true)

      scope =
        admin_user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      assert Permissions.admin?(scope)
    end

    test "returns false for member role" do
      owner = user_fixture()
      organization = organization_fixture(owner.id)

      member_user = user_fixture()
      membership_fixture(organization.id, member_user.id, :member)

      organization = Repo.preload(organization, :memberships, force: true)

      scope =
        member_user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      refute Permissions.admin?(scope)
    end

    test "returns false for viewer role" do
      owner = user_fixture()
      organization = organization_fixture(owner.id)

      viewer_user = user_fixture()
      membership_fixture(organization.id, viewer_user.id, :viewer)

      organization = Repo.preload(organization, :memberships, force: true)

      scope =
        viewer_user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      refute Permissions.admin?(scope)
    end

    test "returns false when no organization context" do
      user = user_fixture()
      scope = Scope.for_user(user)

      refute Permissions.admin?(scope)
    end

    test "returns false for nil scope" do
      refute Permissions.admin?(nil)
    end

    test "works with Socket" do
      user = user_fixture()
      organization = organization_fixture(user.id)
      organization = Repo.preload(organization, :memberships)

      scope =
        user
        |> Scope.for_user()
        |> Scope.put_organization(organization)

      socket = %Socket{assigns: %{current_scope: scope}}

      assert Permissions.admin?(socket)
    end
  end
end
