defmodule ToweropsWeb.DeviceLive.FormTest do
  use ToweropsWeb.ConnCase

  import Mox
  import Phoenix.LiveViewTest

  alias Towerops.Snmp.SnmpMock

  setup :register_and_log_in_user
  setup :verify_on_exit!
  setup :set_mox_global

  setup do
    # Stub SNMP mock globally
    Mox.stub(SnmpMock, :get, fn _target, _oid, _opts -> {:error, :timeout} end)
    Mox.stub(SnmpMock, :walk, fn _target, _oid, _opts -> {:error, :timeout} end)
    Mox.stub(SnmpMock, :get_bulk, fn _target, _oid, _opts -> {:error, :timeout} end)

    :ok
  end

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

    %{organization: organization}
  end

  describe "New Device Form" do
    setup %{organization: organization} do
      {:ok, site} =
        Towerops.Sites.create_site(%{
          name: "Test Site",
          organization_id: organization.id
        })

      %{site: site}
    end

    test "renders new device form", %{conn: conn, organization: organization} do
      {:ok, _view, html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")

      assert html =~ "New Device"
      assert html =~ "Add new device to monitor"
    end

    test "redirects to sites page when no sites exist", %{conn: conn, user: user} do
      # Create organization without sites
      {:ok, empty_org} = Towerops.Organizations.create_organization(%{name: "Empty Org"}, user.id)

      {:ok, _view, html} =
        conn
        |> live(~p"/orgs/#{empty_org.slug}/devices/new")
        |> follow_redirect(conn, ~p"/orgs/#{empty_org.slug}/sites/new")

      assert html =~ "Please create a site before adding a device"
    end

    test "validates form inputs", %{conn: conn, organization: organization} do
      {:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")

      html =
        view
        |> form("#device-form", device: %{name: "", ip_address: ""})
        |> render_change()

      assert html =~ "device-form"
    end

    test "auto-selects single site", %{conn: conn, organization: organization, site: site} do
      {:ok, _view, html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")

      # The form should have the site pre-selected
      assert html =~ site.name
    end

    test "pre-selects site from query param", %{conn: conn, organization: organization} do
      {:ok, site2} =
        Towerops.Sites.create_site(%{
          name: "Test Site 2",
          organization_id: organization.id
        })

      {:ok, _view, html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/new?site_id=#{site2.id}")

      # Should use the query param site
      assert html =~ site2.name
    end

    test "handles no site auto-selection with multiple sites", %{
      conn: conn,
      organization: organization
    } do
      # Create a second site so no auto-selection happens
      {:ok, _site2} =
        Towerops.Sites.create_site(%{
          name: "Test Site 2",
          organization_id: organization.id
        })

      {:ok, _view, html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")

      # Should show both sites in the form
      assert html =~ "Test Site"
      assert html =~ "Test Site 2"
    end
  end

  describe "Edit Device Form" do
    setup %{organization: organization} do
      {:ok, site} =
        Towerops.Sites.create_site(%{
          name: "Test Site",
          organization_id: organization.id
        })

      {:ok, device} =
        Towerops.Devices.create_device(%{
          name: "Router 1",
          ip_address: "192.168.1.1",
          site_id: site.id
        })

      %{site: site, device: device}
    end

    test "renders edit form", %{conn: conn, organization: organization, device: device} do
      {:ok, _view, html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")

      assert html =~ "Edit Device"
      assert html =~ device.name
    end

    test "validates edited form inputs", %{
      conn: conn,
      organization: organization,
      device: device
    } do
      {:ok, view, _html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")

      html =
        view
        |> form("#device-form", device: %{name: ""})
        |> render_change()

      assert html =~ "device-form"
    end

    test "handles save error on edit", %{
      conn: conn,
      organization: organization,
      device: device
    } do
      {:ok, view, _html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")

      # Submit with invalid data
      html =
        view
        |> form("#device-form", device: %{ip_address: "invalid-ip"})
        |> render_submit()

      assert html =~ "must be a valid IPv4 or IPv6 address"
    end

    test "loads current agent assignment if exists", %{
      conn: conn,
      organization: organization,
      device: device
    } do
      # Create an agent token
      {:ok, agent_token, _token} =
        Towerops.Agents.create_agent_token(organization.id, "Test Agent")

      # Assign the agent to the device
      Towerops.Agents.update_device_assignment(device.id, agent_token.id)

      {:ok, _view, html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")

      # Should show the form successfully
      assert html =~ "Edit Device"
    end
  end

  describe "Delete Device" do
    setup %{organization: organization} do
      {:ok, site} =
        Towerops.Sites.create_site(%{
          name: "Test Site",
          organization_id: organization.id
        })

      {:ok, device} =
        Towerops.Devices.create_device(%{
          name: "Router 1",
          ip_address: "192.168.1.1",
          site_id: site.id
        })

      %{site: site, device: device}
    end

    test "deletes device successfully", %{
      conn: conn,
      organization: organization,
      device: device
    } do
      {:ok, view, _html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")

      {:ok, _, html} =
        view
        |> element("button", "Delete Device")
        |> render_click()
        |> follow_redirect(conn, ~p"/orgs/#{organization.slug}/devices")

      assert html =~ "Device deleted successfully"
    end
  end

  describe "Test SNMP Connection" do
    setup %{organization: organization} do
      {:ok, site} =
        Towerops.Sites.create_site(%{
          name: "Test Site",
          organization_id: organization.id
        })

      %{site: site}
    end

    test "tests SNMP connection successfully", %{conn: conn, organization: organization} do
      {:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")

      # Mock successful SNMP test
      expect(SnmpMock, :get, fn _target, _oid, _opts ->
        {:ok, 12_345}
      end)

      # Fill in form with valid data
      view
      |> form("#device-form",
        device: %{
          ip_address: "192.168.1.1",
          snmp_community: "public",
          snmp_version: "2c",
          snmp_port: "161"
        }
      )
      |> render_change()

      # Test connection
      html = render_click(view, "test_snmp")

      # Should show success message in test result
      assert html =~ "test-result" or html =~ "Device"
    end

    test "tests SNMP connection with failure", %{conn: conn, organization: organization} do
      {:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")

      # Mock failed SNMP test
      expect(SnmpMock, :get, fn _target, _oid, _opts ->
        {:error, :timeout}
      end)

      # Fill in form
      view
      |> form("#device-form",
        device: %{
          ip_address: "192.168.1.1",
          snmp_community: "public",
          snmp_version: "2c",
          snmp_port: "161"
        }
      )
      |> render_change()

      # Test connection
      html = render_click(view, "test_snmp")

      # Should show error
      assert html =~ "Connection failed"
    end

    test "validates SNMP test requires IP address", %{conn: conn, organization: organization} do
      {:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")

      # Test without IP
      html = render_click(view, "test_snmp")

      # Should show error
      assert html =~ "IP address is required"
    end

    test "validates SNMP test requires community string", %{
      conn: conn,
      organization: organization
    } do
      {:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")

      # Set IP but not community
      view
      |> form("#device-form", device: %{ip_address: "192.168.1.1"})
      |> render_change()

      html = render_click(view, "test_snmp")

      # Should show error
      assert html =~ "SNMP community string is required"
    end

    test "validates SNMP test rejects empty community string", %{
      conn: conn,
      organization: organization
    } do
      {:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")

      # Set IP with empty community
      view
      |> form("#device-form",
        device: %{ip_address: "192.168.1.1", snmp_community: ""}
      )
      |> render_change()

      html = render_click(view, "test_snmp")

      # Should show error
      assert html =~ "SNMP community string is required"
    end

    test "validates SNMP test rejects invalid IP format", %{
      conn: conn,
      organization: organization
    } do
      {:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")

      # Set invalid IP
      view
      |> form("#device-form",
        device: %{ip_address: "invalid", snmp_community: "public"}
      )
      |> render_change()

      html = render_click(view, "test_snmp")

      # Should show error
      assert html =~ "Invalid IP address format"
    end

    test "normalizes port numbers in SNMP test", %{conn: conn, organization: organization} do
      {:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")

      # Mock SNMP test
      expect(SnmpMock, :get, fn _target, _oid, opts ->
        # Verify port was normalized
        assert opts[:port] == 161
        {:ok, 12_345}
      end)

      # Set string port
      view
      |> form("#device-form",
        device: %{
          ip_address: "192.168.1.1",
          snmp_community: "public",
          snmp_version: "2c",
          snmp_port: "161"
        }
      )
      |> render_change()

      render_click(view, "test_snmp")
    end

    test "handles invalid port in SNMP test", %{conn: conn, organization: organization} do
      {:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")

      # Mock SNMP test
      expect(SnmpMock, :get, fn _target, _oid, opts ->
        # Should default to 161
        assert opts[:port] == 161
        {:ok, 12_345}
      end)

      # Set invalid port
      view
      |> form("#device-form",
        device: %{
          ip_address: "192.168.1.1",
          snmp_community: "public",
          snmp_version: "2c",
          snmp_port: "999999"
        }
      )
      |> render_change()

      render_click(view, "test_snmp")
    end
  end

  describe "Trigger Discovery" do
    setup %{organization: organization} do
      {:ok, site} =
        Towerops.Sites.create_site(%{
          name: "Test Site",
          organization_id: organization.id
        })

      {:ok, device} =
        Towerops.Devices.create_device(%{
          name: "Router 1",
          ip_address: "192.168.1.1",
          site_id: site.id,
          snmp_enabled: true,
          snmp_community: "public",
          snmp_version: "2c"
        })

      %{site: site, device: device}
    end

    test "triggers discovery for SNMP-enabled devices", %{
      conn: conn,
      organization: organization,
      device: device
    } do
      {:ok, view, _html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")

      html = render_click(view, "trigger_discovery")

      assert html =~ "Discovery started"
    end

    test "rejects discovery for non-SNMP device", %{
      conn: conn,
      organization: organization,
      site: site
    } do
      {:ok, non_snmp_device} =
        Towerops.Devices.create_device(%{
          name: "Router 2",
          ip_address: "192.168.1.2",
          site_id: site.id,
          snmp_enabled: false
        })

      {:ok, view, _html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{non_snmp_device.id}/edit")

      html = render_click(view, "trigger_discovery")

      assert html =~ "SNMP is not enabled"
    end
  end

  describe "SNMP Config Changes" do
    setup %{organization: organization} do
      {:ok, site} =
        Towerops.Sites.create_site(%{
          name: "Test Site",
          organization_id: organization.id
        })

      {:ok, device} =
        Towerops.Devices.create_device(%{
          name: "Router 1",
          ip_address: "192.168.1.1",
          site_id: site.id,
          snmp_enabled: true,
          snmp_community: "public",
          snmp_version: "2c",
          snmp_port: 161
        })

      %{site: site, device: device}
    end

    test "triggers discovery when enabling SNMP", %{
      conn: conn,
      organization: organization,
      site: site
    } do
      # Create device without SNMP
      {:ok, device} =
        Towerops.Devices.create_device(%{
          name: "Router 2",
          ip_address: "192.168.1.2",
          site_id: site.id,
          snmp_enabled: false
        })

      {:ok, view, _html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")

      # Switch to SNMP & ICMP mode using the tab selector
      view
      |> element("button", "SNMP & ICMP")
      |> render_click()

      # Now submit with SNMP settings
      {:ok, _, html} =
        view
        |> form("#device-form",
          device: %{
            snmp_community: "public",
            snmp_version: "2c"
          }
        )
        |> render_submit()
        |> follow_redirect(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}")

      assert html =~ "SNMP discovery started in background"
    end

    test "triggers discovery when changing SNMP community", %{
      conn: conn,
      organization: organization,
      device: device
    } do
      {:ok, view, _html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")

      # Change community
      {:ok, _, html} =
        view
        |> form("#device-form", device: %{snmp_community: "private"})
        |> render_submit()
        |> follow_redirect(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}")

      assert html =~ "SNMP discovery started in background"
    end

    test "triggers discovery when changing SNMP version", %{
      conn: conn,
      organization: organization,
      device: device
    } do
      {:ok, view, _html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")

      # Change version
      {:ok, _, html} =
        view
        |> form("#device-form", device: %{snmp_version: "1"})
        |> render_submit()
        |> follow_redirect(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}")

      assert html =~ "SNMP discovery started in background"
    end

    test "triggers discovery when changing SNMP port", %{
      conn: conn,
      organization: organization,
      device: device
    } do
      {:ok, view, _html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")

      # Change port
      {:ok, _, html} =
        view
        |> form("#device-form", device: %{snmp_port: 162})
        |> render_submit()
        |> follow_redirect(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}")

      assert html =~ "SNMP discovery started in background"
    end

    test "triggers discovery when changing IP address", %{
      conn: conn,
      organization: organization,
      device: device
    } do
      {:ok, view, _html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")

      # Change IP
      {:ok, _, html} =
        view
        |> form("#device-form", device: %{ip_address: "192.168.1.100"})
        |> render_submit()
        |> follow_redirect(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}")

      assert html =~ "SNMP discovery started in background"
    end

    test "does not trigger discovery for unrelated changes", %{
      conn: conn,
      organization: organization,
      device: device
    } do
      {:ok, view, _html} =
        live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")

      # Change only name
      {:ok, _, html} =
        view
        |> form("#device-form", device: %{name: "Updated Router"})
        |> render_submit()
        |> follow_redirect(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}")

      # Should not mention discovery
      refute html =~ "SNMP discovery started in background"
      assert html =~ "Device updated successfully"
    end
  end
end
