require "test_helper"

class InvitationsControllerTest < ActionDispatch::IntegrationTest
  test "should show invitation acceptance page with valid token" do
    user = users(:pending_invitation)

    get invitation_path(user.invitation_token)

    assert_response :success
    assert_select "h1", "Accept Invitation"
    assert_select "input[type=password]", count: 2
  end

  test "should redirect with invalid token" do
    get invitation_path("invalid_token")

    assert_redirected_to root_path
    assert_equal "Invalid or expired invitation link.", flash[:alert]
  end

  test "should redirect with expired token" do
    user = users(:pending_invitation)
    user.update(invitation_sent_at: 15.days.ago)

    get invitation_path(user.invitation_token)

    assert_redirected_to root_path
    assert_equal "Invalid or expired invitation link.", flash[:alert]
  end

  test "should accept invitation with valid password" do
    user = users(:pending_invitation)

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "securepassword123"
      }
    }

    user.reload
    assert_not_nil user.invitation_accepted_at
    assert_nil user.invitation_token
    assert_equal user.id, session[:user_id]
    assert_redirected_to root_path
  end

  test "should not accept invitation with short password" do
    user = users(:pending_invitation)

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "short",
        password_confirmation: "short"
      }
    }

    user.reload
    assert_nil user.invitation_accepted_at
    assert_not_nil user.invitation_token
    assert_response :unprocessable_entity
  end

  test "should not accept invitation with mismatched passwords" do
    user = users(:pending_invitation)

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "differentpassword"
      }
    }

    user.reload
    assert_nil user.invitation_accepted_at
    assert_response :unprocessable_entity
  end

  test "should not accept expired invitation" do
    user = users(:pending_invitation)
    user.update(invitation_sent_at: 15.days.ago)

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "securepassword123"
      }
    }

    assert_redirected_to root_path
    assert_equal "Invalid or expired invitation link.", flash[:alert]
  end

  test "should redirect admin to dashboard after accepting invitation" do
    inviter = users(:admin_user)
    pending_admin = User.create!(
      email: "pending-admin@example.com",
      name: "Pending Admin",
      role: :admin,
      primary_language: "en",
      invitation_token: "pending_admin_token",
      invitation_sent_at: 1.day.ago,
      invited_by: inviter,
      password: "password123456"
    )

    patch accept_invitation_path(pending_admin.invitation_token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "securepassword123"
      }
    }

    pending_admin.reload
    assert_not_nil pending_admin.invitation_accepted_at
    assert_nil pending_admin.invitation_token
    assert_equal pending_admin.id, session[:user_id]
    assert_redirected_to admin_root_path
  end

  # Entry activation tests
  test "should activate approved entries on invitation acceptance" do
    user = users(:pending_invitation)

    # Create approved entry for this user
    approved_entry = Entry.create!(
      category: :word,
      fi: "Test word",
      status: :approved,
      requested_by: user
    )

    # Create requested entry (should not be activated)
    requested_entry = Entry.create!(
      category: :word,
      fi: "Requested word",
      status: :requested,
      requested_by: user
    )

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "securepassword123"
      }
    }

    approved_entry.reload
    requested_entry.reload

    assert_equal "active", approved_entry.status
    assert_equal "requested", requested_entry.status
  end

  test "should activate multiple approved entries on invitation acceptance" do
    user = users(:pending_invitation)

    # Create multiple approved entries
    entries = 3.times.map do |i|
      Entry.create!(
        category: :word,
        fi: "Word #{i}",
        status: :approved,
        requested_by: user
      )
    end

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "securepassword123"
      }
    }

    entries.each do |entry|
      entry.reload
      assert_equal "active", entry.status
    end
  end

  test "should not activate entries for other users" do
    user = users(:pending_invitation)
    other_user = users(:admin_user)

    # Create approved entry for another user
    other_entry = Entry.create!(
      category: :word,
      fi: "Other user word",
      status: :approved,
      requested_by: other_user
    )

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "securepassword123"
      }
    }

    other_entry.reload
    assert_equal "approved", other_entry.status
  end

  test "should handle invitation acceptance with no entries" do
    user = users(:pending_invitation)

    # Ensure the user has no entries
    Entry.where(requested_by: user).delete_all
    assert_equal 0, Entry.where(requested_by: user).count

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "securepassword123"
      }
    }

    assert_redirected_to root_path
    user.reload
    assert_not_nil user.invitation_accepted_at
  end

  # Security tests
  test "should only permit password parameters" do
    user = users(:pending_invitation)
    original_email = user.email
    original_name = user.name
    original_role = user.role

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "securepassword123",
        email: "hacker@example.com",
        name: "Hacker Name",
        role: "admin"
      }
    }

    user.reload
    assert_equal original_email, user.email
    assert_equal original_name, user.name
    assert_equal original_role, user.role
  end

  test "should not log in user when password validation fails" do
    user = users(:pending_invitation)

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "short",
        password_confirmation: "short"
      }
    }

    assert_nil session[:user_id]
  end

  # Edge cases
  test "should handle already accepted invitation" do
    user = users(:pending_invitation)
    user.update!(invitation_accepted_at: Time.current, invitation_token: nil)

    patch accept_invitation_path("some_token"), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "securepassword123"
      }
    }

    assert_redirected_to root_path
    assert_equal "Invalid or expired invitation link.", flash[:alert]
  end

  test "should set invitation_accepted_at timestamp" do
    user = users(:pending_invitation)
    assert_nil user.invitation_accepted_at

    freeze_time do
      patch accept_invitation_path(user.invitation_token), params: {
        user: {
          password: "securepassword123",
          password_confirmation: "securepassword123"
        }
      }

      user.reload
      assert_in_delta Time.current.to_i, user.invitation_accepted_at.to_i, 2
    end
  end

  test "should clear invitation token after acceptance" do
    user = users(:pending_invitation)
    token = user.invitation_token
    assert_not_nil token

    patch accept_invitation_path(token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "securepassword123"
      }
    }

    user.reload
    assert_nil user.invitation_token
  end

  test "should require password to be present" do
    user = users(:pending_invitation)

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: nil,
        password_confirmation: nil
      }
    }

    # has_secure_password validates password presence when setting password
    user.reload
    assert_nil user.invitation_accepted_at
  end

  test "should show validation errors for short password" do
    user = users(:pending_invitation)

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "short",
        password_confirmation: "short"
      }
    }

    assert_response :unprocessable_entity
    assert_select "div.text-red-700", text: /too short/i
  end

  test "should show validation errors for mismatched passwords" do
    user = users(:pending_invitation)

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "differentpassword"
      }
    }

    assert_response :unprocessable_entity
    assert_select "div.text-red-700", text: /confirmation/i
  end

  test "should authenticate with new password after acceptance" do
    user = users(:pending_invitation)

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "mynewpassword123",
        password_confirmation: "mynewpassword123"
      }
    }

    user.reload
    assert user.authenticate("mynewpassword123")
    assert_not user.authenticate("oldpassword")
  end

  test "should log in user immediately after acceptance" do
    user = users(:pending_invitation)

    # First visit the invitation page to establish session
    get invitation_path(user.invitation_token)
    assert_nil session[:user_id]

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "securepassword123"
      }
    }

    assert_equal user.id, session[:user_id]
  end

  test "should show welcome message with user name" do
    user = users(:pending_invitation)

    patch accept_invitation_path(user.invitation_token), params: {
      user: {
        password: "securepassword123",
        password_confirmation: "securepassword123"
      }
    }

    assert_match /Welcome to Sanasto Wiki, #{user.name}!/, flash[:notice]
  end
end
