defmodule Towerops.Accounts.BrowserSessionTest do
  use ExUnit.Case, async: true

  alias Towerops.Accounts.BrowserSession

  describe "create_changeset/2" do
    test "valid changeset with all required fields" do
      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        ip_address: "192.168.1.1",
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      assert changeset.valid?
    end

    test "valid changeset with optional device metadata" do
      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        ip_address: "192.168.1.1",
        device_name: "Chrome on macOS",
        browser_name: "Chrome",
        browser_version: "120.0.0",
        os_name: "macOS",
        os_version: "14.2",
        device_type: "desktop",
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      assert changeset.valid?
      assert changeset.changes.device_name == "Chrome on macOS"
      assert changeset.changes.browser_name == "Chrome"
      assert changeset.changes.device_type == "desktop"
    end

    test "valid changeset with location data" do
      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        ip_address: "192.168.1.1",
        country_code: "US",
        country_name: "United States",
        city_name: "San Francisco",
        subdivision_1_name: "California",
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      assert changeset.valid?
      assert changeset.changes.country_name == "United States"
      assert changeset.changes.city_name == "San Francisco"
    end

    test "requires user_id" do
      attrs = %{
        user_token_id: Ecto.UUID.generate(),
        ip_address: "192.168.1.1",
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      refute changeset.valid?
      assert %{user_id: ["can't be blank"]} = errors_on(changeset)
    end

    test "requires user_token_id" do
      attrs = %{
        user_id: Ecto.UUID.generate(),
        ip_address: "192.168.1.1",
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      refute changeset.valid?
      assert %{user_token_id: ["can't be blank"]} = errors_on(changeset)
    end

    test "requires ip_address" do
      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      refute changeset.valid?
      assert %{ip_address: ["can't be blank"]} = errors_on(changeset)
    end

    test "requires last_activity_at" do
      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        ip_address: "192.168.1.1",
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      refute changeset.valid?
      assert %{last_activity_at: ["can't be blank"]} = errors_on(changeset)
    end

    test "requires expires_at" do
      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        ip_address: "192.168.1.1",
        last_activity_at: ~U[2026-01-29 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      refute changeset.valid?
      assert %{expires_at: ["can't be blank"]} = errors_on(changeset)
    end

    test "validates ip_address max length (45 chars for IPv6)" do
      # IPv6 address can be up to 45 characters with full notation
      valid_ipv6 = "2001:0db8:0000:0000:0000:8a2e:0370:7334"
      assert String.length(valid_ipv6) == 39

      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        ip_address: valid_ipv6,
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      assert changeset.valid?

      # Test too long
      too_long = String.duplicate("x", 46)

      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        ip_address: too_long,
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      refute changeset.valid?
      assert %{ip_address: ["should be at most 45 character(s)"]} = errors_on(changeset)
    end

    test "validates device_name max length (255 chars)" do
      too_long = String.duplicate("x", 256)

      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        ip_address: "192.168.1.1",
        device_name: too_long,
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      refute changeset.valid?
      assert %{device_name: ["should be at most 255 character(s)"]} = errors_on(changeset)
    end

    test "validates browser_name max length (100 chars)" do
      too_long = String.duplicate("x", 101)

      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        ip_address: "192.168.1.1",
        browser_name: too_long,
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      refute changeset.valid?
      assert %{browser_name: ["should be at most 100 character(s)"]} = errors_on(changeset)
    end

    test "validates os_name max length (100 chars)" do
      too_long = String.duplicate("x", 101)

      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        ip_address: "192.168.1.1",
        os_name: too_long,
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      refute changeset.valid?
      assert %{os_name: ["should be at most 100 character(s)"]} = errors_on(changeset)
    end

    test "validates device_type inclusion (desktop, mobile, tablet)" do
      # Test valid values
      for valid_type <- ["desktop", "mobile", "tablet"] do
        attrs = %{
          user_id: Ecto.UUID.generate(),
          user_token_id: Ecto.UUID.generate(),
          ip_address: "192.168.1.1",
          device_type: valid_type,
          last_activity_at: ~U[2026-01-29 19:00:00Z],
          expires_at: ~U[2026-02-12 19:00:00Z]
        }

        changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
        assert changeset.valid?
      end

      # Test invalid value
      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        ip_address: "192.168.1.1",
        device_type: "smartwatch",
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      refute changeset.valid?
      assert %{device_type: ["is invalid"]} = errors_on(changeset)
    end

    test "allows nil device_type" do
      attrs = %{
        user_id: Ecto.UUID.generate(),
        user_token_id: Ecto.UUID.generate(),
        ip_address: "192.168.1.1",
        device_type: nil,
        last_activity_at: ~U[2026-01-29 19:00:00Z],
        expires_at: ~U[2026-02-12 19:00:00Z]
      }

      changeset = BrowserSession.create_changeset(%BrowserSession{}, attrs)
      assert changeset.valid?
    end
  end

  describe "touch_changeset/1" do
    test "updates last_activity_at to current time" do
      session = %BrowserSession{
        last_activity_at: ~U[2026-01-29 10:00:00Z]
      }

      changeset = BrowserSession.touch_changeset(session)

      assert changeset.valid?
      assert changeset.changes.last_activity_at
      # Verify it's a recent timestamp (within last 5 seconds)
      assert DateTime.diff(changeset.changes.last_activity_at, DateTime.utc_now()) < 5
    end
  end

  describe "anonymize_changeset/1" do
    test "sets user_id to nil and records anonymized_at timestamp" do
      session = %BrowserSession{
        user_id: Ecto.UUID.generate(),
        anonymized_at: nil
      }

      changeset = BrowserSession.anonymize_changeset(session)

      assert changeset.valid?
      assert changeset.changes.user_id == nil
      assert changeset.changes.anonymized_at
      # Verify it's a recent timestamp
      assert DateTime.diff(changeset.changes.anonymized_at, DateTime.utc_now()) < 5
    end
  end

  # Helper function to convert changeset errors to a map
  defp errors_on(changeset) do
    Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
      Regex.replace(~r"%{(\w+)}", message, fn _, key ->
        opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
      end)
    end)
  end
end
