defmodule ToweropsWeb.AgentLiveTest do
  use ToweropsWeb.ConnCase

  import Ecto.Query
  import ExUnit.CaptureLog
  import Phoenix.LiveViewTest

  alias Towerops.Agents

  setup :register_and_log_in_user

  setup %{user: user} do
    {:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)

    %{organization: organization}
  end

  describe "Index - Basic Functionality" do
    test "lists all agent tokens", %{conn: conn, organization: organization} do
      {:ok, _agent_token, _token} = Agents.create_agent_token(organization.id, "Agent 1")

      {:ok, _view, html} = live(conn, ~p"/agents")

      assert html =~ "Remote Agents"
      assert html =~ "Agent 1"
    end

    test "displays empty state when no agents", %{conn: conn, organization: _organization} do
      {:ok, _view, html} = live(conn, ~p"/agents")

      assert html =~ "No agents"
      assert html =~ "Get started by creating your first remote agent"
    end

    test "creates new agent and shows token modal", %{conn: conn, organization: _organization} do
      {:ok, view, _html} = live(conn, ~p"/agents")

      html =
        view
        |> form("#new-agent-form form", %{name: "Test Agent"})
        |> render_submit()

      assert html =~ "Agent Setup Instructions"
      assert html =~ "Test Agent"
      assert html =~ "Authentication Token"
    end

    test "deletes agent", %{conn: conn, organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Agent 1")

      {:ok, view, _html} = live(conn, ~p"/agents")

      html =
        view
        |> element("button", "Delete")
        |> render_click()

      assert html =~ "Agent deleted successfully"

      # Verify agent was actually deleted
      assert is_nil(Towerops.Repo.get(Agents.AgentToken, agent_token.id))
    end

    test "closes token modal", %{conn: conn, organization: _organization} do
      {:ok, view, _html} = live(conn, ~p"/agents")

      # Create agent to show modal
      view
      |> form("#new-agent-form form", %{name: "Test Agent"})
      |> render_submit()

      # Close modal by clicking the primary button (bg-blue-600 class indicates primary button)
      html =
        view
        |> element("button.bg-blue-600[phx-click='close_token_modal']")
        |> render_click()

      refute html =~ "Agent Created Successfully"
    end

    test "requires authentication", %{organization: _organization} do
      conn = build_conn()
      {:error, redirect} = live(conn, ~p"/agents")

      assert {:redirect, %{to: path}} = redirect
      assert path == ~p"/users/log-in"
    end

    test "displays agent with warning status", %{conn: conn, organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Agent 1")

      # Update last_seen_at to 3 minutes ago (warning status)
      three_minutes_ago = DateTime.add(DateTime.utc_now(), -180, :second)

      Agents.update_agent_token_heartbeat(agent_token.id, "127.0.0.1")

      Towerops.Repo.update_all(
        from(t in Agents.AgentToken, where: t.id == ^agent_token.id),
        set: [last_seen_at: three_minutes_ago]
      )

      {:ok, _view, html} = live(conn, ~p"/agents")

      assert html =~ "Warning"
      assert html =~ "3m ago"
    end

    test "displays agent with offline status", %{conn: conn, organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Agent 1")

      # Update last_seen_at to 10 minutes ago (offline status)
      ten_minutes_ago = DateTime.add(DateTime.utc_now(), -600, :second)

      Agents.update_agent_token_heartbeat(agent_token.id, "127.0.0.1")

      Towerops.Repo.update_all(
        from(t in Agents.AgentToken, where: t.id == ^agent_token.id),
        set: [last_seen_at: ten_minutes_ago]
      )

      {:ok, _view, html} = live(conn, ~p"/agents")

      assert html =~ "Offline"
      assert html =~ "10m ago"
    end

    test "displays agent last seen time in hours", %{conn: conn, organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Agent 1")

      # Update last_seen_at to 2 hours ago
      two_hours_ago = DateTime.add(DateTime.utc_now(), -7200, :second)

      Agents.update_agent_token_heartbeat(agent_token.id, "127.0.0.1")

      Towerops.Repo.update_all(
        from(t in Agents.AgentToken, where: t.id == ^agent_token.id),
        set: [last_seen_at: two_hours_ago]
      )

      {:ok, _view, html} = live(conn, ~p"/agents")

      assert html =~ "2h ago"
    end

    test "displays agent last seen time in days", %{conn: conn, organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Agent 1")

      # Update last_seen_at to 2 days ago
      two_days_ago = DateTime.add(DateTime.utc_now(), -172_800, :second)

      Agents.update_agent_token_heartbeat(agent_token.id, "127.0.0.1")

      Towerops.Repo.update_all(
        from(t in Agents.AgentToken, where: t.id == ^agent_token.id),
        set: [last_seen_at: two_days_ago]
      )

      {:ok, _view, html} = live(conn, ~p"/agents")

      assert html =~ "2d ago"
    end
  end

  describe "Index - Token Management" do
    test "shows setup instructions for existing agent", %{conn: conn, organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Test Agent")

      {:ok, view, _html} = live(conn, ~p"/agents")

      html =
        view
        |> element("button[phx-click='show_setup'][phx-value-id='#{agent_token.id}']")
        |> render_click()

      assert html =~ "Agent Setup Instructions"
      assert html =~ "Test Agent"
      assert html =~ "Authentication Token"
    end
  end

  describe "Index - Cloud Pollers (Superuser)" do
    setup %{user: user} do
      # Make user a superuser
      Towerops.Repo.update!(Ecto.Changeset.change(user, %{is_superuser: true}))
      :ok
    end

    test "superuser can create cloud poller", %{conn: conn} do
      {:ok, view, _html} = live(conn, ~p"/agents")

      html =
        view
        |> form("#new-agent-form form", %{name: "Cloud Poller 1", is_cloud_poller: "true"})
        |> render_submit()

      assert html =~ "Cloud poller created successfully"
      assert html =~ "Agent Setup Instructions"
      assert html =~ "Cloud Poller 1"
    end

    test "superuser can see cloud pollers section", %{conn: conn} do
      {:ok, _cloud_poller, _token} = Agents.create_cloud_poller("Cloud Poller 1")

      {:ok, _view, html} = live(conn, ~p"/agents")

      assert html =~ "Cloud Pollers"
      assert html =~ "Cloud Poller 1"
    end

    test "superuser can set global default cloud poller", %{conn: conn} do
      {:ok, cloud_poller, _token} = Agents.create_cloud_poller("Default Poller")

      {:ok, view, _html} = live(conn, ~p"/agents")

      # Update the selected dropdown value using the event handler
      render_hook(view, "update_selected_global_default", %{"agent_token_id" => cloud_poller.id})

      # Save the selection
      html =
        view
        |> element("button[phx-click='save_global_default']")
        |> render_click()

      assert html =~ "Global default cloud poller set successfully"
    end

    test "superuser can clear global default cloud poller", %{conn: conn} do
      {:ok, cloud_poller, _token} = Agents.create_cloud_poller("Default Poller")
      {:ok, _} = Towerops.Settings.set_global_default_cloud_poller(cloud_poller.id)

      {:ok, view, _html} = live(conn, ~p"/agents")

      # Select empty value using the event handler
      render_hook(view, "update_selected_global_default", %{"agent_token_id" => ""})

      # Save the selection
      html =
        view
        |> element("button[phx-click='save_global_default']")
        |> render_click()

      assert html =~ "Global default cloud poller cleared"
    end

    test "superuser sees error when setting non-existent agent as default", %{conn: conn} do
      non_existent_id = Ecto.UUID.generate()

      {:ok, view, _html} = live(conn, ~p"/agents")

      # Update dropdown to non-existent ID
      render_hook(view, "update_selected_global_default", %{"agent_token_id" => non_existent_id})

      # Try to save - should fail validation
      html =
        view
        |> element("button[phx-click='save_global_default']")
        |> render_click()

      assert html =~ "Selected agent no longer exists"
    end

    test "deleting global default cloud poller clears the setting", %{conn: conn} do
      {:ok, cloud_poller, _token} = Agents.create_cloud_poller("Default Poller")
      {:ok, _} = Towerops.Settings.set_global_default_cloud_poller(cloud_poller.id)

      {:ok, view, _html} = live(conn, ~p"/agents")

      # Delete the cloud poller
      html =
        view
        |> element("button[phx-click='delete_agent'][phx-value-id='#{cloud_poller.id}']")
        |> render_click()

      assert html =~ "Agent deleted successfully"

      # Verify global default was cleared
      assert Towerops.Settings.get_global_default_cloud_poller() == nil
    end
  end

  describe "Index - Cloud Pollers (Regular User)" do
    test "regular user cannot create cloud poller", %{conn: conn} do
      {:ok, view, _html} = live(conn, ~p"/agents")

      # Regular users don't have the is_cloud_poller checkbox in the form,
      # so we need to test the backend permission check directly
      # by triggering the event with is_cloud_poller parameter
      html = render_hook(view, "create_agent", %{"name" => "Test", "is_cloud_poller" => "true"})

      assert html =~ "Only superadmins can create cloud pollers"
    end

    test "regular user does not see cloud pollers section", %{conn: conn} do
      {:ok, _cloud_poller, _token} = Agents.create_cloud_poller("Cloud Poller 1")

      {:ok, _view, html} = live(conn, ~p"/agents")

      refute html =~ "Cloud Pollers"
      refute html =~ "Global Default Cloud Poller"
    end

    test "regular user cannot set global default cloud poller", %{conn: conn} do
      {:ok, cloud_poller, _token} = Agents.create_cloud_poller("Default Poller")

      # First make the user a superuser temporarily to set the global default
      # Then we'll test that a regular user can't access the save_global_default event
      {:ok, _} = Towerops.Settings.set_global_default_cloud_poller(cloud_poller.id)

      {:ok, view, html} = live(conn, ~p"/agents")

      # Regular user should not see the cloud poller UI at all
      refute html =~ "Global Default Cloud Poller"

      # Try to trigger save_global_default event directly - should be blocked by permission check
      # This simulates an attacker trying to call the event handler
      capture_log(fn ->
        result = render_click(view, "save_global_default", %{})
        # Should get an error or be rejected
        assert result =~ "Only superadmins can set the global default cloud poller" or
                 result =~ "Remote Agents"
      end)
    end
  end

  describe "Index - Device Counts" do
    test "displays device counts for agents", %{conn: conn, organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Test Agent")

      # Create a site and device assigned to this agent
      {:ok, site} =
        Towerops.Sites.create_site(%{
          name: "Test Site",
          address: "123 Test St",
          organization_id: organization.id
        })

      {:ok, device} =
        Towerops.Devices.create_device(%{
          name: "Test Device",
          ip_address: "192.168.1.100",
          site_id: site.id,
          organization_id: organization.id
        })

      Agents.assign_device_to_agent(agent_token.id, device.id)

      {:ok, _view, html} = live(conn, ~p"/agents")

      # Should show device count (displays as "1 total" in template)
      assert html =~ "1 total"
      assert html =~ "1 direct"
    end

    test "displays zero when no devices assigned", %{conn: conn, organization: organization} do
      {:ok, _agent_token, _token} = Agents.create_agent_token(organization.id, "Test Agent")

      {:ok, _view, html} = live(conn, ~p"/agents")

      # Displays as "0 total" in template
      assert html =~ "0 total"
      assert html =~ "0 direct"
    end
  end

  describe "Index - Error Handling" do
    test "handles agent creation failure", %{conn: conn} do
      {:ok, view, _html} = live(conn, ~p"/agents")

      # Try to create agent with empty name
      html =
        view
        |> form("#new-agent-form form", %{name: ""})
        |> render_submit()

      assert html =~ "Failed to create agent"
    end
  end

  describe "Index - Multiple Agents" do
    test "displays multiple agents", %{conn: conn, organization: organization} do
      {:ok, _agent1, _token1} = Agents.create_agent_token(organization.id, "Agent 1")
      {:ok, _agent2, _token2} = Agents.create_agent_token(organization.id, "Agent 2")
      {:ok, _agent3, _token3} = Agents.create_agent_token(organization.id, "Agent 3")

      {:ok, _view, html} = live(conn, ~p"/agents")

      assert html =~ "Agent 1"
      assert html =~ "Agent 2"
      assert html =~ "Agent 3"
    end

    test "deleting one agent keeps others", %{conn: conn, organization: organization} do
      {:ok, agent1, _token1} = Agents.create_agent_token(organization.id, "Agent 1")
      {:ok, _agent2, _token2} = Agents.create_agent_token(organization.id, "Agent 2")

      {:ok, view, _html} = live(conn, ~p"/agents")

      html =
        view
        |> element("button[phx-click='delete_agent'][phx-value-id='#{agent1.id}']")
        |> render_click()

      assert html =~ "Agent deleted successfully"
      assert html =~ "Agent 2"
      refute html =~ "Agent 1"
    end
  end

  describe "Edit" do
    test "loads edit page with existing agent name", %{conn: conn, organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Old Agent Name")

      {:ok, _view, html} = live(conn, ~p"/agents/#{agent_token.id}/edit")

      assert html =~ "Edit Agent"
      assert html =~ "Old Agent Name"
      assert html =~ "Update the agent name"
    end

    test "validates empty name shows error on submit", %{conn: conn, organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Test Agent")

      {:ok, view, _html} = live(conn, ~p"/agents/#{agent_token.id}/edit")

      html =
        view
        |> form("#edit-agent-form", agent_token: %{name: ""})
        |> render_submit()

      assert html =~ "can&#39;t be blank"

      # Verify agent was not updated
      agent = Agents.get_agent_token!(agent_token.id)
      assert agent.name == "Test Agent"
    end

    test "successful save updates agent and redirects to show page", %{
      conn: conn,
      organization: organization
    } do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Old Name")

      {:ok, view, _html} = live(conn, ~p"/agents/#{agent_token.id}/edit")

      view
      |> form("#edit-agent-form", agent_token: %{name: "New Name"})
      |> render_submit()

      # Verify redirect to show page with flash message
      assert_redirect(view, ~p"/agents/#{agent_token.id}")

      # Verify agent was updated
      updated_agent = Agents.get_agent_token!(agent_token.id)
      assert updated_agent.name == "New Name"
    end

    test "cancel button navigates back to show page", %{conn: conn, organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Test Agent")

      {:ok, _view, html} = live(conn, ~p"/agents/#{agent_token.id}/edit")

      assert html =~ "Cancel"

      # Find cancel link and verify it points to show page
      assert html =~
               ~p"/agents/#{agent_token.id}"
    end

    test "requires authentication", %{organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Test Agent")

      conn = build_conn()
      {:error, redirect} = live(conn, ~p"/agents/#{agent_token.id}/edit")

      assert {:redirect, %{to: path}} = redirect
      assert path == ~p"/users/log-in"
    end

    test "verifies agent belongs to organization", %{conn: conn, organization: organization, user: user} do
      # Create another organization
      {:ok, other_org} = Towerops.Organizations.create_organization(%{name: "Other Org"}, user.id, bypass_limits: true)
      {:ok, agent_token, _token} = Agents.create_agent_token(other_org.id, "Other Agent")

      # Scope session to the first organization while preserving auth
      conn = Plug.Conn.put_session(conn, "current_organization_id", organization.id)

      # Try to edit agent from other organization - capture_log suppresses expected Router exception log
      capture_log(fn ->
        assert_raise Ecto.NoResultsError, fn ->
          live(conn, ~p"/agents/#{agent_token.id}/edit")
        end
      end)
    end
  end

  describe "Show - Superadmin Cross-Org Access" do
    setup %{user: user} do
      Towerops.Repo.update!(Ecto.Changeset.change(user, %{is_superuser: true}))
      :ok
    end

    test "superadmin can view agent from another organization", %{conn: conn, organization: organization, user: user} do
      {:ok, other_org} = Towerops.Organizations.create_organization(%{name: "Other Org"}, user.id, bypass_limits: true)
      {:ok, agent_token, _token} = Agents.create_agent_token(other_org.id, "Other Org Agent")

      conn = Plug.Conn.put_session(conn, "current_organization_id", organization.id)

      {:ok, _view, html} = live(conn, ~p"/agents/#{agent_token.id}")

      assert html =~ "Other Org Agent"
    end

    test "superadmin can edit agent from another organization", %{conn: conn, organization: organization, user: user} do
      {:ok, other_org} = Towerops.Organizations.create_organization(%{name: "Other Org"}, user.id, bypass_limits: true)
      {:ok, agent_token, _token} = Agents.create_agent_token(other_org.id, "Other Org Agent")

      conn = Plug.Conn.put_session(conn, "current_organization_id", organization.id)

      {:ok, _view, html} = live(conn, ~p"/agents/#{agent_token.id}/edit")

      assert html =~ "Edit Agent"
      assert html =~ "Other Org Agent"
    end
  end

  describe "Show - Restart Agent" do
    test "superuser sees restart button and can restart agent", %{conn: conn, organization: organization, user: user} do
      Towerops.Repo.update!(Ecto.Changeset.change(user, %{is_superuser: true}))
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Test Agent")

      {:ok, view, html} = live(conn, ~p"/agents/#{agent_token.id}")

      assert html =~ "Restart"

      # Subscribe to lifecycle topic to verify broadcast
      Phoenix.PubSub.subscribe(Towerops.PubSub, "agent:#{agent_token.id}:lifecycle")

      view
      |> element("button", "Restart")
      |> render_click()

      assert_receive :restart_requested
      assert render(view) =~ "Restart command sent"
    end

    test "regular user does not see restart button", %{conn: conn, organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Test Agent")

      {:ok, _view, html} = live(conn, ~p"/agents/#{agent_token.id}")

      refute html =~ "Restart"
    end
  end

  describe "Show - Update Agent" do
    test "superuser sees update button", %{conn: conn, organization: organization, user: user} do
      Towerops.Repo.update!(Ecto.Changeset.change(user, %{is_superuser: true}))
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Test Agent")

      {:ok, view, _html} = live(conn, ~p"/agents/#{agent_token.id}")

      assert has_element?(view, "button", "Update")
    end

    test "superuser clicking update shows error when no releases available", %{
      conn: conn,
      organization: organization,
      user: user
    } do
      Towerops.Repo.update!(Ecto.Changeset.change(user, %{is_superuser: true}))
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Test Agent")

      {:ok, view, _html} = live(conn, ~p"/agents/#{agent_token.id}")

      html =
        view
        |> element("button", "Update")
        |> render_click()

      assert html =~ "Failed to fetch latest release"
    end

    test "regular user does not see update button", %{conn: conn, organization: organization} do
      {:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Test Agent")

      {:ok, view, _html} = live(conn, ~p"/agents/#{agent_token.id}")

      refute has_element?(view, "button", "Update")
    end
  end

  describe "Show - Regular User Cross-Org Access" do
    test "regular user cannot view agent from another organization", %{conn: conn, organization: organization, user: user} do
      {:ok, other_org} = Towerops.Organizations.create_organization(%{name: "Other Org"}, user.id, bypass_limits: true)
      {:ok, agent_token, _token} = Agents.create_agent_token(other_org.id, "Other Org Agent")

      conn = Plug.Conn.put_session(conn, "current_organization_id", organization.id)

      capture_log(fn ->
        assert_raise Ecto.NoResultsError, fn ->
          live(conn, ~p"/agents/#{agent_token.id}")
        end
      end)
    end
  end
end
