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

  import Towerops.AccountsFixtures
  import Towerops.OrganizationsFixtures

  alias Towerops.ApiTokens
  alias Towerops.Sites

  setup do
    user = user_fixture()
    organization = organization_fixture(user.id)

    {:ok, {_token, raw_token}} =
      ApiTokens.create_api_token(%{
        organization_id: organization.id,
        user_id: user.id,
        name: "Test Token"
      })

    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 sites for authenticated organization", %{
      conn: conn,
      organization: organization
    } do
      {:ok, site1} =
        Sites.create_site(%{name: "Site Alpha", organization_id: organization.id})

      {:ok, site2} =
        Sites.create_site(%{name: "Site Beta", organization_id: organization.id})

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

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

      site_ids = Enum.map(sites, & &1["id"])
      assert site1.id in site_ids
      assert site2.id in site_ids

      site_json = List.first(sites)
      assert Map.has_key?(site_json, "id")
      assert Map.has_key?(site_json, "name")
      assert Map.has_key?(site_json, "location")
      assert Map.has_key?(site_json, "snmp_community")
      assert Map.has_key?(site_json, "inserted_at")
    end

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

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

    test "does not return sites from other organizations", %{conn: conn} do
      other_user = user_fixture()
      other_org = organization_fixture(other_user.id)

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

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

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

  describe "create/2" do
    test "creates site with valid attributes", %{conn: conn, organization: organization} do
      site_params = %{
        "site" => %{
          "name" => "Main Office",
          "location" => "New York, NY",
          "snmp_community" => "public"
        }
      }

      conn = post(conn, ~p"/api/v1/sites", site_params)

      assert %{
               "id" => id,
               "name" => "Main Office",
               "location" => "New York, NY",
               "snmp_community" => "public"
             } = json_response(conn, 201)

      assert is_binary(id)

      # Verify the site was created in the correct organization
      site = Sites.get_site!(id)
      assert site.organization_id == organization.id
    end

    test "creates site with minimal attributes", %{conn: conn} do
      site_params = %{
        "site" => %{
          "name" => "Minimal Site"
        }
      }

      conn = post(conn, ~p"/api/v1/sites", site_params)

      assert %{
               "id" => _id,
               "name" => "Minimal Site",
               "location" => nil,
               "snmp_community" => nil
             } = json_response(conn, 201)
    end

    test "returns 422 with invalid site params", %{conn: conn} do
      site_params = %{
        "site" => %{
          "name" => ""
        }
      }

      conn = post(conn, ~p"/api/v1/sites", site_params)

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

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

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

  describe "show/2" do
    test "returns site when it belongs to organization", %{
      conn: conn,
      organization: organization
    } do
      {:ok, site} =
        Sites.create_site(%{
          name: "Test Site",
          location: "Boston, MA",
          snmp_community: "private",
          organization_id: organization.id
        })

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

      assert %{
               "id" => id,
               "name" => "Test Site",
               "location" => "Boston, MA",
               "snmp_community" => "private"
             } = json_response(conn, 200)

      assert id == site.id
    end

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

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

    test "returns 403 when site belongs to different organization", %{conn: conn} do
      other_user = user_fixture()
      other_org = organization_fixture(other_user.id)

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

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

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

  describe "update/2" do
    test "updates site with valid attributes", %{conn: conn, organization: organization} do
      {:ok, site} =
        Sites.create_site(%{
          name: "Original Name",
          location: "Chicago, IL",
          organization_id: organization.id
        })

      update_params = %{
        "site" => %{
          "name" => "Updated Name",
          "location" => "Boston, MA"
        }
      }

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

      assert %{
               "id" => id,
               "name" => "Updated Name",
               "location" => "Boston, MA"
             } = json_response(conn, 200)

      assert id == site.id
    end

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

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

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

    test "returns 403 when site belongs to different organization", %{conn: conn} do
      other_user = user_fixture()
      other_org = organization_fixture(other_user.id)

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

      update_params = %{
        "site" => %{
          "name" => "Hacked Name"
        }
      }

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

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

    test "returns 422 with invalid update params", %{conn: conn, organization: organization} do
      {:ok, site} =
        Sites.create_site(%{name: "Test Site", organization_id: organization.id})

      update_params = %{
        "site" => %{
          "name" => ""
        }
      }

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

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

    test "returns 400 when site parameter is missing", %{
      conn: conn,
      organization: organization
    } do
      {:ok, site} =
        Sites.create_site(%{name: "Test Site", organization_id: organization.id})

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

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

  describe "delete/2" do
    test "deletes site successfully", %{conn: conn, organization: organization} do
      {:ok, site} =
        Sites.create_site(%{name: "Doomed Site", organization_id: organization.id})

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

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

      # Verify site is deleted
      assert_raise Ecto.NoResultsError, fn ->
        Sites.get_site!(site.id)
      end
    end

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

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

    test "returns 403 when site belongs to different organization", %{conn: conn} do
      other_user = user_fixture()
      other_org = organization_fixture(other_user.id)

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

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

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

  describe "authentication" do
    test "returns 401 without authorization header" do
      conn =
        build_conn()
        |> put_req_header("accept", "application/json")
        |> get(~p"/api/v1/sites")

      assert %{"error" => _message} = json_response(conn, 401)
    end

    test "returns 401 with invalid token" do
      conn =
        build_conn()
        |> put_req_header("authorization", "Bearer towerops_invalid_token")
        |> put_req_header("accept", "application/json")
        |> get(~p"/api/v1/sites")

      assert %{"error" => _message} = json_response(conn, 401)
    end
  end
end
