defmodule VaultTest do
  use ExUnit.Case
  doctest Vault

  describe "initialization" do
    test "prevents double initialization in same process" do
      Vault.init(current_user: "alice")

      assert_raise RuntimeError,
                   "Vault already initialized in this process",
                   fn -> Vault.init(role: "admin") end
    end

    test "prevents child initialization when ancestor already has vault" do
      Vault.init(tenant: "acme")

      Task.async(fn ->
        assert_raise RuntimeError,
                     "Cannot initialize vault: ancestor process already has vault initialized",
                     fn -> Vault.init(should_fail: true) end
      end)
      |> Task.await()
    end

    test "handles complex nested data structures" do
      complex_data = %{
        user: %{
          id: 123,
          profile: %{name: "Alice", preferences: %{theme: "dark", lang: "en"}},
          permissions: ["read", "write"]
        },
        request: %{id: "req_456", timestamp: DateTime.utc_now()},
        metadata: %{version: "1.0", source: "web"}
      }

      Vault.init(complex_data)

      assert Vault.get(:user).id == 123
      assert Vault.get(:user).profile.name == "Alice"
      assert Vault.get(:user).profile.preferences.theme == "dark"
      assert Vault.get(:request).id == "req_456"
    end
  end

  describe "hierarchical process inheritance" do
    test "only leaf nodes can initialize vault initially" do
      # Process hierarchy:
      #
      #       ┌───┐
      #       │ A │
      #       └─┬─┘
      #         │
      #    ┌────┼────┐
      #    │         │
      #  ┌─▼─┐     ┌─▼─┐
      #  │ B │     │ C │
      #  └───┘     └─┬─┘
      #              │
      #            ┌─▼─┐
      #            │ D │
      #            └───┘
      #
      # Initialization order: B, C successfully. D, A fail due to ancestors with vaults.

      test_pid = self()

      # Process A spawns B and C
      {:ok, b_pid} =
        Task.start_link(fn ->
          receive do
            :init ->
              assert :ok = Vault.init(node: "B")
          end

          receive do
            :shutdown -> :ok
          end
        end)

      {:ok, c_pid} =
        Task.start_link(fn ->
          # C spawns D
          {:ok, d_pid} =
            Task.start_link(fn ->
              receive do
                :try_init ->
                  assert_raise RuntimeError,
                               "Cannot initialize vault: ancestor process already has vault initialized",
                               fn ->
                                 Vault.init(node: "D")
                               end
              end

              receive do
                :shutdown -> :ok
              end
            end)

          send(test_pid, {:d_pid, d_pid})

          receive do
            :init ->
              assert :ok = Vault.init(node: "C")
          end

          receive do
            :shutdown ->
              send(d_pid, :shutdown)
              :ok
          end
        end)

      # Get D's PID
      assert_receive {:d_pid, d_pid}

      # Test sequence: First init leaf nodes B and C
      send(b_pid, :init)
      send(c_pid, :init)

      # Now try to init D (should fail because C has vault)
      send(d_pid, :try_init)

      # A is the test process itself, so it cannot init when descendants have vaults
      # This is expected to succeed because vault doesn't prevent ancestor initialization
      # when descendants have vaults - it only prevents child initialization when ancestors have vaults
      Vault.init(node: "A")
    end

    test "deeply nested process tree access" do
      Vault.init(
        session_id: "sess_123",
        current_user: %{id: 42, role: "admin"},
        tenant: "acme_corp"
      )

      # Level 1 child
      Task.async(fn ->
        assert Vault.get(:session_id) == "sess_123"
        assert Vault.get(:current_user).role == "admin"

        # Level 2 child
        Task.async(fn ->
          assert Vault.get(:tenant) == "acme_corp"
          assert Vault.get(:current_user).id == 42

          # Level 3 child
          Task.async(fn ->
            assert Vault.get(:session_id) == "sess_123"
          end)
          |> Task.await()
        end)
        |> Task.await()
      end)
      |> Task.await()
    end

    test "works with Task.Supervisor spawned processes" do
      Vault.init(
        correlation_id: "corr_789",
        api_version: "v2",
        rate_limit: %{requests: 100, window: 3600}
      )

      {:ok, supervisor_pid} = Task.Supervisor.start_link()

      tasks =
        for i <- 1..5 do
          Task.Supervisor.async(supervisor_pid, fn ->
            # Each task can access the vault data
            assert Vault.get(:correlation_id) == "corr_789"
            assert Vault.get(:api_version) == "v2"
            assert Vault.get(:rate_limit).requests == 100

            # Simulate some work
            Process.sleep(10)
            "task_#{i}_done"
          end)
        end

      results = Enum.map(tasks, &Task.await/1)
      assert length(results) == 5
      assert Enum.all?(results, &String.contains?(&1, "task_"))
    end

    test "accessible through process ancestry" do
      Vault.init(secret: "accessible_through_ancestry")

      # Linked process
      Task.async(fn ->
        assert Vault.get(:secret) == "accessible_through_ancestry"
      end)
      |> Task.await()

      # Unlinked process
      {:ok, _pid} =
        Task.start(fn ->
          assert Vault.get(:secret) == "accessible_through_ancestry"
        end)

      # Give the unlinked task time to complete
      Process.sleep(50)
    end

    test "Tasks have access to vault" do
      Vault.init(data: "accessible_through_ancestry", current_user: "alice")

      # Task.start now has access through ancestry
      {:ok, _pid} =
        Task.start(fn ->
          assert Vault.get(:data) == "accessible_through_ancestry"
          assert Vault.get(:current_user) == "alice"
          assert :data in Vault.keys()
          assert :current_user in Vault.keys()

          assert Enum.sort(Vault.to_list()) == [
                   current_user: "alice",
                   data: "accessible_through_ancestry"
                 ]
        end)

      # Give the unlinked task time to complete
      Process.sleep(50)

      # Linked process still has access
      Task.async(fn ->
        assert Vault.get(:data) == "accessible_through_ancestry"
        assert Vault.get(:current_user) == "alice"
      end)
      |> Task.await()
    end
  end

  describe "unsafe operations" do
    test "unsafe_put allows modification without initialization" do
      # No initialization
      assert Vault.get(:emergency) == nil

      Vault.unsafe_put(:emergency, true)
      assert Vault.get(:emergency) == true

      Vault.unsafe_put(:debug_mode, %{enabled: true, level: :verbose})
      assert Vault.get(:debug_mode).enabled == true
      assert Vault.get(:debug_mode).level == :verbose
    end

    test "unsafe_merge combines multiple keys" do
      Vault.init(existing: "data", keep_me: true)

      Vault.unsafe_merge(%{
        new_key: "new_value",
        another: %{nested: "structure"},
        existing: "overwritten"
      })

      assert Vault.get(:keep_me) == true
      assert Vault.get(:new_key) == "new_value"
      assert Vault.get(:another).nested == "structure"
      assert Vault.get(:existing) == "overwritten"
    end

    test "unsafe_update with complex transformations" do
      Vault.init(
        counters: %{requests: 0, errors: 0},
        user_activity: %{last_seen: nil, actions_count: 5}
      )

      # Increment request counter
      Vault.unsafe_update(:counters, %{}, fn counters ->
        Map.update(counters, :requests, 1, &(&1 + 1))
      end)

      # Update user activity
      now = DateTime.utc_now()

      Vault.unsafe_update(:user_activity, %{}, fn activity ->
        activity
        |> Map.put(:last_seen, now)
        |> Map.update(:actions_count, 0, &(&1 + 1))
      end)

      assert Vault.get(:counters).requests == 1
      assert Vault.get(:counters).errors == 0
      assert Vault.get(:user_activity).last_seen == now
      assert Vault.get(:user_activity).actions_count == 6
    end
  end
end
