defmodule Mv.Accounts.UserEmailSyncTest do
  @moduledoc """
  Tests for email synchronization from User to Member.
  When a user and member are linked, email changes should sync bidirectionally.
  User.email is the source of truth when linking occurs.
  """
  use Mv.DataCase, async: false
  alias Mv.Accounts
  alias Mv.Membership

  setup do
    system_actor = Mv.Helpers.SystemActor.get_system_actor()
    %{actor: system_actor}
  end

  describe "User email synchronization to linked Member" do
    @valid_user_attrs %{
      email: "user@example.com"
    }

    @valid_member_attrs %{
      first_name: "John",
      last_name: "Doe",
      email: "member@example.com"
    }

    test "updating user email syncs to linked member", %{actor: actor} do
      # Create a member
      {:ok, member} = Membership.create_member(@valid_member_attrs, actor: actor)
      assert member.email == "member@example.com"

      # Create a user linked to the member
      {:ok, user} =
        Accounts.create_user(Map.put(@valid_user_attrs, :member, %{id: member.id}), actor: actor)

      # Verify initial state - member email should be overridden by user email
      {:ok, member_after_link} = Ash.get(Mv.Membership.Member, member.id, actor: actor)
      assert member_after_link.email == "user@example.com"

      # Update user email
      {:ok, updated_user} =
        Accounts.update_user(user, %{email: "newemail@example.com"}, actor: actor)

      assert to_string(updated_user.email) == "newemail@example.com"

      # Verify member email was also updated
      {:ok, synced_member} = Ash.get(Mv.Membership.Member, member.id, actor: actor)
      assert synced_member.email == "newemail@example.com"
    end

    test "creating user linked to member overrides member email", %{actor: actor} do
      # Create a member with their own email
      {:ok, member} = Membership.create_member(@valid_member_attrs, actor: actor)
      assert member.email == "member@example.com"

      # Create a user linked to this member
      {:ok, user} =
        Accounts.create_user(Map.put(@valid_user_attrs, :member, %{id: member.id}), actor: actor)

      assert to_string(user.email) == "user@example.com"
      assert user.member_id == member.id

      # Verify member email was overridden with user email
      {:ok, updated_member} = Ash.get(Mv.Membership.Member, member.id, actor: actor)
      assert updated_member.email == "user@example.com"
    end

    test "linking user to existing member syncs user email to member", %{actor: actor} do
      # Create a standalone member
      {:ok, member} = Membership.create_member(@valid_member_attrs, actor: actor)
      assert member.email == "member@example.com"

      # Create a standalone user
      {:ok, user} = Accounts.create_user(@valid_user_attrs, actor: actor)
      assert to_string(user.email) == "user@example.com"
      assert user.member_id == nil

      # Link the user to the member
      {:ok, linked_user} = Accounts.update_user(user, %{member: %{id: member.id}}, actor: actor)
      assert linked_user.member_id == member.id

      # Verify member email was overridden with user email
      {:ok, synced_member} = Ash.get(Mv.Membership.Member, member.id, actor: actor)
      assert synced_member.email == "user@example.com"
    end

    test "updating user email when no member linked does not error", %{actor: actor} do
      # Create a standalone user without member link
      {:ok, user} = Accounts.create_user(@valid_user_attrs, actor: actor)
      assert to_string(user.email) == "user@example.com"
      assert user.member_id == nil

      # Update user email - should work fine without error
      {:ok, updated_user} =
        Accounts.update_user(user, %{email: "newemail@example.com"}, actor: actor)

      assert to_string(updated_user.email) == "newemail@example.com"
      assert updated_user.member_id == nil
    end

    test "unlinking user from member does not sync email", %{actor: actor} do
      # Create member
      {:ok, member} = Membership.create_member(@valid_member_attrs, actor: actor)

      # Create user linked to member
      {:ok, user} =
        Accounts.create_user(Map.put(@valid_user_attrs, :member, %{id: member.id}), actor: actor)

      assert user.member_id == member.id

      # Verify member email was synced
      {:ok, synced_member} = Ash.get(Mv.Membership.Member, member.id, actor: actor)
      assert synced_member.email == "user@example.com"

      # Unlink user from member
      {:ok, unlinked_user} = Accounts.update_user(user, %{member: nil}, actor: actor)
      assert unlinked_user.member_id == nil

      # Member email should remain unchanged after unlinking
      {:ok, member_after_unlink} = Ash.get(Mv.Membership.Member, member.id, actor: actor)
      assert member_after_unlink.email == "user@example.com"
    end

    test "admin_set_password with email change syncs to linked member", %{actor: actor} do
      # Create member and user linked to it (with password so admin_set_password applies)
      {:ok, member} = Membership.create_member(@valid_member_attrs, actor: actor)

      {:ok, user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:register_with_password, %{
          email: "user@example.com",
          password: "initialpass123"
        })
        |> Ash.create(actor: actor)

      {:ok, user} =
        user
        |> Ash.Changeset.for_update(:update_user, %{member: %{id: member.id}})
        |> Ash.update(actor: actor)

      assert user.member_id == member.id
      {:ok, m} = Ash.get(Mv.Membership.Member, member.id, actor: actor)
      assert m.email == "user@example.com"

      # Change both email and password via admin_set_password (e.g. user form "Change Password")
      {:ok, updated_user} =
        user
        |> Ash.Changeset.for_update(:admin_set_password, %{
          email: "newemail@example.com",
          password: "newpassword123"
        })
        |> Ash.update(actor: actor)

      assert to_string(updated_user.email) == "newemail@example.com"

      # Member email must be synced (Option A: SyncUserEmailToMember on admin_set_password)
      {:ok, synced_member} = Ash.get(Mv.Membership.Member, member.id, actor: actor)
      assert synced_member.email == "newemail@example.com"
    end
  end

  describe "AshAuthentication compatibility" do
    test "AshAuthentication password strategy still works with email" do
      # This test ensures that the email field remains accessible for password auth
      email = "test@example.com"
      password = "securepassword123"

      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      # Create user with password strategy (simulating registration)
      {:ok, user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:register_with_password, %{
          email: email,
          password: password
        })
        |> Ash.create(actor: system_actor)

      assert to_string(user.email) == email
      assert user.hashed_password != nil

      # Verify we can sign in with email
      {:ok, signed_in_user} =
        Mv.Accounts.User
        |> Ash.Query.for_read(:sign_in_with_password, %{
          email: email,
          password: password
        })
        |> Ash.read_one(actor: system_actor)

      assert signed_in_user.id == user.id
      assert to_string(signed_in_user.email) == email
    end

    test "AshAuthentication OIDC strategy still works with email" do
      # This test ensures the OIDC flow can still set email
      user_info = %{
        "preferred_username" => "oidc@example.com",
        "sub" => "oidc-user-123"
      }

      oauth_tokens = %{"access_token" => "mock_token"}

      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      # Simulate OIDC registration
      {:ok, user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:register_with_rauthy, %{
          user_info: user_info,
          oauth_tokens: oauth_tokens
        })
        |> Ash.create(actor: system_actor)

      assert to_string(user.email) == "oidc@example.com"
      assert user.oidc_id == "oidc-user-123"
    end
  end
end
