defmodule ToweropsWeb.Api.V1.DevicesControllerTest do
  use ToweropsWeb.ConnCase, async: true

  import Towerops.AccountsFixtures
  import Towerops.DevicesFixtures
  import Towerops.OrganizationsFixtures

  alias Towerops.ApiTokens
  alias Towerops.Sites

  setup do
    # Create organization with owner
    user = user_fixture()
    organization = organization_fixture(user.id)

    # Create API token for authentication
    {:ok, {_token, raw_token}} =
      ApiTokens.create_api_token(%{
        organization_id: organization.id,
        user_id: user.id,
        name: "Test Token"
      })

    # Setup authenticated connection
    conn =
      build_conn()
      |> put_req_header("authorization", "Bearer #{raw_token}")
      |> put_req_header("accept", "application/json")

    %{
      conn: conn,
      user: user,
      organization: organization,
      raw_token: raw_token
    }
  end

  describe "index/2" do
    test "lists all devices for authenticated organization", %{
      conn: conn,
      organization: organization
    } do
      device1 = device_fixture(%{organization_id: organization.id})
      device2 = device_fixture(%{organization_id: organization.id})

      conn = get(conn, ~p"/api/v1/devices")

      assert %{"devices" => devices} = json_response(conn, 200)
      assert length(devices) == 2

      device_ids = Enum.map(devices, & &1["id"])
      assert device1.id in device_ids
      assert device2.id in device_ids

      # Check response structure
      device_json = List.first(devices)
      assert Map.has_key?(device_json, "id")
      assert Map.has_key?(device_json, "name")
      assert Map.has_key?(device_json, "ip_address")
      assert Map.has_key?(device_json, "site_id")
      assert Map.has_key?(device_json, "monitoring_enabled")
      assert Map.has_key?(device_json, "snmp_enabled")
      assert Map.has_key?(device_json, "inserted_at")
    end

    test "filters devices by site_id when provided", %{
      conn: conn,
      organization: organization
    } do
      {:ok, site1} = Sites.create_site(%{name: "Site 1", organization_id: organization.id})
      {:ok, site2} = Sites.create_site(%{name: "Site 2", organization_id: organization.id})

      device_in_site1 = device_fixture(%{organization_id: organization.id, site_id: site1.id})
      device_in_site2 = device_fixture(%{organization_id: organization.id, site_id: site2.id})
      _device_no_site = device_fixture(%{organization_id: organization.id})

      conn = get(conn, ~p"/api/v1/devices", site_id: site1.id)

      assert %{"devices" => devices} = json_response(conn, 200)
      assert length(devices) == 1
      assert List.first(devices)["id"] == device_in_site1.id
      refute device_in_site2.id in Enum.map(devices, & &1["id"])
    end

    test "returns empty list when organization has no devices", %{conn: conn} do
      conn = get(conn, ~p"/api/v1/devices")

      assert %{"devices" => []} = json_response(conn, 200)
    end

    test "does not return devices from other organizations", %{conn: conn} do
      # Create another organization with a device
      other_user = user_fixture()
      other_org = organization_fixture(other_user.id)
      _other_device = device_fixture(%{organization_id: other_org.id})

      conn = get(conn, ~p"/api/v1/devices")

      assert %{"devices" => devices} = json_response(conn, 200)
      assert Enum.empty?(devices)
    end
  end

  describe "create/2" do
    test "creates device with valid attributes", %{conn: conn, organization: organization} do
      device_params = %{
        "device" => %{
          "name" => "Test Router",
          "ip_address" => "192.168.1.1",
          "snmp_enabled" => true,
          "snmp_community" => "public"
        }
      }

      conn = post(conn, ~p"/api/v1/devices", device_params)

      assert %{
               "id" => id,
               "name" => "Test Router",
               "ip_address" => "192.168.1.1",
               "organization_id" => org_id,
               "snmp_enabled" => true
             } = json_response(conn, 201)

      assert org_id == organization.id
      assert is_binary(id)
    end

    test "creates device with site_id", %{conn: conn, organization: organization} do
      # Enable sites for this organization
      {:ok, organization} = Towerops.Organizations.update_organization(organization, %{use_sites: true})
      {:ok, site} = Sites.create_site(%{name: "Test Site", organization_id: organization.id})

      device_params = %{
        "device" => %{
          "name" => "Test Router",
          "ip_address" => "192.168.1.1",
          "site_id" => site.id
        }
      }

      conn = post(conn, ~p"/api/v1/devices", device_params)

      assert %{"id" => _id, "site_id" => site_id} = json_response(conn, 201)
      assert site_id == site.id
    end

    test "defaults organization_id to authenticated organization", %{
      conn: conn,
      organization: organization
    } do
      device_params = %{
        "device" => %{
          "name" => "Test Router",
          "ip_address" => "192.168.1.1"
        }
      }

      conn = post(conn, ~p"/api/v1/devices", device_params)

      assert %{"organization_id" => org_id} = json_response(conn, 201)
      assert org_id == organization.id
    end

    test "returns 403 when site belongs to different organization", %{conn: conn, organization: organization} do
      # Enable sites for the authenticated organization
      {:ok, _organization} = Towerops.Organizations.update_organization(organization, %{use_sites: true})

      # Create another organization with a site
      other_user = user_fixture()
      other_org = organization_fixture(other_user.id)
      {:ok, other_org} = Towerops.Organizations.update_organization(other_org, %{use_sites: true})

      {:ok, other_site} =
        Sites.create_site(%{name: "Other Site", organization_id: other_org.id})

      device_params = %{
        "device" => %{
          "name" => "Test Router",
          "ip_address" => "192.168.1.1",
          "site_id" => other_site.id
        }
      }

      conn = post(conn, ~p"/api/v1/devices", device_params)

      assert %{"error" => "Access denied to this site"} = json_response(conn, 403)
    end

    test "returns 403 when site_id does not exist", %{conn: conn, organization: organization} do
      # Enable sites for this organization
      {:ok, _organization} = Towerops.Organizations.update_organization(organization, %{use_sites: true})

      device_params = %{
        "device" => %{
          "name" => "Test Router",
          "ip_address" => "192.168.1.1",
          "site_id" => Ecto.UUID.generate()
        }
      }

      conn = post(conn, ~p"/api/v1/devices", device_params)

      assert %{"error" => "Site not found"} = json_response(conn, 403)
    end

    test "returns 422 with invalid device params", %{conn: conn} do
      device_params = %{
        "device" => %{
          "name" => "",
          "ip_address" => "invalid-ip"
        }
      }

      conn = post(conn, ~p"/api/v1/devices", device_params)

      assert %{"errors" => errors} = json_response(conn, 422)
      assert Map.has_key?(errors, "name") or Map.has_key?(errors, "ip_address")
    end

    test "returns 400 when device parameter is missing", %{conn: conn} do
      conn = post(conn, ~p"/api/v1/devices", %{})

      assert %{"error" => "Missing 'device' parameter"} = json_response(conn, 400)
    end
  end

  describe "show/2" do
    test "returns device when it belongs to organization", %{
      conn: conn,
      organization: organization
    } do
      device = device_fixture(%{organization_id: organization.id})

      conn = get(conn, ~p"/api/v1/devices/#{device.id}")

      assert %{
               "id" => id,
               "name" => name,
               "ip_address" => ip,
               "monitoring_enabled" => _,
               "snmp_enabled" => _,
               "snmp_version" => _,
               "snmp_port" => _,
               "description" => _,
               "check_interval_seconds" => _
             } = json_response(conn, 200)

      assert id == device.id
      assert name == device.name
      assert ip == device.ip_address
    end

    test "returns 404 when device does not exist", %{conn: conn} do
      conn = get(conn, ~p"/api/v1/devices/#{Ecto.UUID.generate()}")

      assert %{"error" => "Device not found"} = json_response(conn, 404)
    end

    test "returns 403 when device belongs to different organization", %{conn: conn} do
      # Create another organization with a device
      other_user = user_fixture()
      other_org = organization_fixture(other_user.id)
      other_device = device_fixture(%{organization_id: other_org.id})

      conn = get(conn, ~p"/api/v1/devices/#{other_device.id}")

      assert %{"error" => "Access denied to this device"} = json_response(conn, 403)
    end
  end

  describe "update/2" do
    test "updates device with valid attributes", %{conn: conn, organization: organization} do
      device = device_fixture(%{organization_id: organization.id})

      update_params = %{
        "device" => %{
          "name" => "Updated Router",
          "ip_address" => "192.168.1.100"
        }
      }

      conn = patch(conn, ~p"/api/v1/devices/#{device.id}", update_params)

      assert %{
               "id" => id,
               "name" => "Updated Router",
               "ip_address" => "192.168.1.100"
             } = json_response(conn, 200)

      assert id == device.id
    end

    test "returns 404 when device does not exist", %{conn: conn} do
      update_params = %{
        "device" => %{
          "name" => "Updated Router"
        }
      }

      conn = patch(conn, ~p"/api/v1/devices/#{Ecto.UUID.generate()}", update_params)

      assert %{"error" => "Device not found"} = json_response(conn, 404)
    end

    test "returns 403 when device belongs to different organization", %{conn: conn} do
      # Create another organization with a device
      other_user = user_fixture()
      other_org = organization_fixture(other_user.id)
      other_device = device_fixture(%{organization_id: other_org.id})

      update_params = %{
        "device" => %{
          "name" => "Updated Router"
        }
      }

      conn = patch(conn, ~p"/api/v1/devices/#{other_device.id}", update_params)

      assert %{"error" => "Access denied to this device"} = json_response(conn, 403)
    end

    test "returns 422 with invalid update params", %{conn: conn, organization: organization} do
      device = device_fixture(%{organization_id: organization.id})

      update_params = %{
        "device" => %{
          "name" => "",
          "ip_address" => "invalid-ip"
        }
      }

      conn = patch(conn, ~p"/api/v1/devices/#{device.id}", update_params)

      assert %{"errors" => errors} = json_response(conn, 422)
      assert Map.has_key?(errors, "name") or Map.has_key?(errors, "ip_address")
    end

    test "returns 400 when device parameter is missing", %{
      conn: conn,
      organization: organization
    } do
      device = device_fixture(%{organization_id: organization.id})

      conn = patch(conn, ~p"/api/v1/devices/#{device.id}", %{})

      assert %{"error" => "Missing 'device' parameter"} = json_response(conn, 400)
    end
  end

  describe "delete/2" do
    test "deletes device successfully", %{conn: conn, organization: organization} do
      device = device_fixture(%{organization_id: organization.id})

      conn = delete(conn, ~p"/api/v1/devices/#{device.id}")

      assert %{"success" => true} = json_response(conn, 200)

      # Verify device is deleted
      assert_raise Ecto.NoResultsError, fn ->
        Towerops.Devices.get_device!(device.id)
      end
    end

    test "returns 404 when device does not exist", %{conn: conn} do
      conn = delete(conn, ~p"/api/v1/devices/#{Ecto.UUID.generate()}")

      assert %{"error" => "Device not found"} = json_response(conn, 404)
    end

    test "returns 403 when device belongs to different organization", %{conn: conn} do
      # Create another organization with a device
      other_user = user_fixture()
      other_org = organization_fixture(other_user.id)
      other_device = device_fixture(%{organization_id: other_org.id})

      conn = delete(conn, ~p"/api/v1/devices/#{other_device.id}")

      assert %{"error" => "Access denied to this device"} = json_response(conn, 403)
    end
  end
end
