defmodule ToweropsWeb.Api.AccountDataControllerTest do
  use ToweropsWeb.ConnCase, async: true

  import Towerops.AccountsFixtures

  alias Towerops.Admin
  alias Towerops.Alerts
  alias Towerops.Devices
  alias Towerops.Organizations

  setup :register_and_log_in_user

  describe "GET /api/v1/account/data" do
    test "requires authentication", %{conn: _conn} do
      # Build a new conn without authentication
      conn = build_conn()
      conn = get(conn, ~p"/api/v1/account/data")
      assert response(conn, 302)
      assert redirected_to(conn) == ~p"/users/log-in"
    end

    test "returns JSON data with correct content type", %{conn: conn} do
      conn = get(conn, ~p"/api/v1/account/data")
      assert response(conn, 200)
      assert get_resp_header(conn, "content-type") == ["application/json; charset=utf-8"]
    end

    test "sets content-disposition header for download", %{conn: conn, user: user} do
      conn = get(conn, ~p"/api/v1/account/data")
      [disposition] = get_resp_header(conn, "content-disposition")
      assert disposition =~ "attachment"
      assert disposition =~ "towerops-data-#{user.id}"
      assert disposition =~ ".json"
    end

    test "includes profile data in response", %{conn: conn, user: user} do
      conn = get(conn, ~p"/api/v1/account/data")
      data = json_response(conn, 200)

      assert data["profile"]["id"] == user.id
      assert data["profile"]["email"] == user.email
      assert data["profile"]["first_name"] == user.first_name
      assert data["profile"]["last_name"] == user.last_name
      # Default timezone is "UTC" even if user record has nil
      assert data["profile"]["timezone"] in ["UTC", user.timezone]
      assert data["profile"]["is_superuser"] == user.is_superuser
    end

    test "includes organizations data", %{conn: conn, user: user} do
      {:ok, org} = Organizations.create_organization(%{name: "Test Org"}, user.id)

      conn = get(conn, ~p"/api/v1/account/data")
      data = json_response(conn, 200)

      assert length(data["organizations"]) == 1
      [org_data] = data["organizations"]
      assert org_data["id"] == org.id
      assert org_data["name"] == "Test Org"
      assert org_data["role"] == "owner"
    end

    test "includes devices data with redacted SNMP community", %{conn: conn, user: user} do
      {:ok, org} = Organizations.create_organization(%{name: "Test Org"}, user.id)

      {:ok, site} =
        Towerops.Sites.create_site(%{
          name: "Test Site",
          organization_id: org.id
        })

      {:ok, device} =
        Devices.create_device(%{
          name: "Test Router",
          ip_address: "192.168.1.1",
          site_id: site.id,
          organization_id: org.id,
          snmp_enabled: true,
          snmp_community: "secret-community-string"
        })

      conn = get(conn, ~p"/api/v1/account/data")
      data = json_response(conn, 200)

      assert length(data["devices"]) == 1
      [device_data] = data["devices"]
      assert device_data["id"] == device.id
      assert device_data["name"] == "Test Router"
      assert device_data["ip_address"] == "192.168.1.1"
      # SNMP community should be redacted for security
      assert device_data["snmp_community"] == "[REDACTED]"
      assert device_data["organization"]["name"] == "Test Org"
      assert device_data["site"]["name"] == "Test Site"
    end

    test "returns empty devices when user has no organizations", %{conn: conn} do
      conn = get(conn, ~p"/api/v1/account/data")
      data = json_response(conn, 200)

      assert data["devices"] == []
    end

    test "includes alerts data from last 90 days", %{conn: conn, user: user} do
      {:ok, org} = Organizations.create_organization(%{name: "Test Org"}, user.id)

      {:ok, site} =
        Towerops.Sites.create_site(%{
          name: "Test Site",
          organization_id: org.id
        })

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

      # Create a recent alert
      {:ok, alert} =
        Alerts.create_alert(%{
          device_id: device.id,
          alert_type: :device_down,
          triggered_at: DateTime.utc_now(),
          message: "Device down"
        })

      conn = get(conn, ~p"/api/v1/account/data")
      data = json_response(conn, 200)

      assert length(data["alerts"]) == 1
      [alert_data] = data["alerts"]
      assert alert_data["id"] == alert.id
      assert alert_data["alert_type"] == "device_down"
      assert alert_data["message"] == "Device down"
      assert alert_data["device"]["name"] == "Test Router"
    end

    test "includes all alerts regardless of age (filtered by inserted_at, not triggered_at)", %{
      conn: conn,
      user: user
    } do
      {:ok, org} = Organizations.create_organization(%{name: "Test Org"}, user.id)

      {:ok, site} =
        Towerops.Sites.create_site(%{
          name: "Test Site",
          organization_id: org.id
        })

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

      # Create an alert with an old triggered_at (120 days ago)
      # Note: The API filters by inserted_at (when record was created), not triggered_at
      {:ok, alert} =
        Alerts.create_alert(%{
          device_id: device.id,
          alert_type: :device_down,
          triggered_at: DateTime.add(DateTime.utc_now(), -120, :day),
          message: "Old triggered time but recent record"
        })

      conn = get(conn, ~p"/api/v1/account/data")
      data = json_response(conn, 200)

      # Alert should be included because it was recently inserted (even though triggered long ago)
      assert length(data["alerts"]) == 1
      assert hd(data["alerts"])["id"] == alert.id
    end

    test "returns empty alerts when user has no organizations", %{conn: conn} do
      conn = get(conn, ~p"/api/v1/account/data")
      data = json_response(conn, 200)

      assert data["alerts"] == []
    end

    test "includes audit logs data", %{conn: conn, user: user} do
      # Create superuser for audit log
      superuser = user_fixture(%{superuser: true})

      {:ok, _log} =
        Admin.create_audit_log(%{
          action: "impersonate_start",
          superuser_id: superuser.id,
          target_user_id: user.id,
          ip_address: "127.0.0.1"
        })

      # Sleep briefly to ensure different timestamps
      Process.sleep(50)

      conn = get(conn, ~p"/api/v1/account/data")
      data = json_response(conn, 200)

      assert length(data["audit_logs"]) == 2

      # Find the logs by action (ordering might vary slightly)
      export_log = Enum.find(data["audit_logs"], &(&1["action"] == "user_data_exported"))
      impersonate_log = Enum.find(data["audit_logs"], &(&1["action"] == "impersonate_start"))

      # Verify export log
      assert export_log
      assert export_log["actor_email"] == user.email

      # Verify impersonation log
      assert impersonate_log
      assert impersonate_log["target_user_id"] == user.id
      assert impersonate_log["actor_email"] == superuser.email
    end

    test "includes export metadata", %{conn: conn} do
      conn = get(conn, ~p"/api/v1/account/data")
      data = json_response(conn, 200)

      assert data["export_info"]["format"] == "JSON"
      assert data["export_info"]["gdpr_article"] == "Article 15 - Right to Access"
      assert is_binary(data["export_info"]["exported_at"])
    end

    test "creates audit log entry for data export", %{conn: conn, user: user} do
      # Count existing audit logs
      initial_count = user.id |> Admin.list_audit_logs_for_user() |> length()

      conn = get(conn, ~p"/api/v1/account/data")
      assert response(conn, 200)

      # Should have one more audit log
      logs = Admin.list_audit_logs_for_user(user.id)
      assert length(logs) == initial_count + 1

      # Most recent log should be the data export
      [latest_log | _] = logs
      assert latest_log.action == "user_data_exported"
      assert latest_log.superuser_id == user.id
    end

    test "handles user with multiple organizations", %{conn: conn, user: user} do
      {:ok, _org1} = Organizations.create_organization(%{name: "Org 1"}, user.id)

      # Create second org by adding membership instead of creating new one
      # (to avoid subscription limit issues in tests)
      other_user = user_fixture()
      {:ok, org2} = Organizations.create_organization(%{name: "Org 2"}, other_user.id)

      {:ok, _membership} =
        Organizations.create_membership(%{
          organization_id: org2.id,
          user_id: user.id,
          role: :member
        })

      conn = get(conn, ~p"/api/v1/account/data")
      data = json_response(conn, 200)

      assert length(data["organizations"]) == 2
      org_names = data["organizations"] |> Enum.map(& &1["name"]) |> Enum.sort()
      assert org_names == ["Org 1", "Org 2"]
    end

    test "handles user as member (not owner) of organization", %{conn: conn, user: user} do
      # Create another user who owns the org
      owner = user_fixture()
      {:ok, org} = Organizations.create_organization(%{name: "Owner's Org"}, owner.id)

      # Add current user as member
      {:ok, _membership} =
        Organizations.create_membership(%{
          organization_id: org.id,
          user_id: user.id,
          role: :member
        })

      conn = get(conn, ~p"/api/v1/account/data")
      data = json_response(conn, 200)

      assert length(data["organizations"]) == 1
      [org_data] = data["organizations"]
      assert org_data["name"] == "Owner's Org"
      assert org_data["role"] == "member"
    end
  end
end
